From 2412e62e93b80d3916c6f0674bf4ff48effc1f88 Mon Sep 17 00:00:00 2001 From: Mike Cousins Date: Mon, 12 Jul 2021 19:08:37 -0400 Subject: [PATCH] fix: officially support Python 3.6 (#40) --- .github/workflows/ci.yml | 22 ++++++++++++++++------ CONTRIBUTING.md | 4 ++-- README.md | 15 +++++++++------ decoy/__init__.py | 3 +-- decoy/core.py | 3 +-- decoy/spy.py | 1 - poetry.lock | 18 ++++++++++++++++-- pyproject.toml | 11 +++++++---- tests/common.py | 2 +- tests/test_matchers.py | 6 ++---- tests/test_spy.py | 17 ++++++++++++++++- 11 files changed, 71 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b52539a..7c6b21b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,11 +10,11 @@ env: jobs: test: name: "Test Python ${{ matrix.python-version }} on ${{ matrix.os }}" - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.7", "3.8", "3.9"] + os: [Ubuntu, Windows, macOS] + python-version: ["3.6", "3.7", "3.8", "3.9"] steps: - name: "Check out repository" uses: actions/checkout@v2 @@ -26,14 +26,18 @@ jobs: - name: "Set up dependency cache" uses: actions/cache@v2 + if: ${{ matrix.os != 'Windows' || matrix.python-version != 3.6 }} with: key: deps-${{ secrets.GH_CACHE }}-${{ runner.os }}-python${{ matrix.python-version }}-${{ hashFiles('**/*.lock') }} path: | ${{ env.PIP_CACHE_DIR }} ${{ env.POETRY_CACHE_DIR }} + - name: "Install poetry" + run: pip install poetry + - name: "Install dependencies" - run: pip install poetry && poetry install + run: poetry install - name: "Run tests" run: poetry run pytest @@ -58,8 +62,11 @@ jobs: ${{ env.PIP_CACHE_DIR }} ${{ env.POETRY_CACHE_DIR }} + - name: "Install poetry" + run: pip install poetry + - name: "Install dependencies" - run: pip install poetry && poetry install + run: poetry install - name: "Check formatting" run: poetry run black --check . @@ -91,8 +98,11 @@ jobs: ${{ env.PIP_CACHE_DIR }} ${{ env.POETRY_CACHE_DIR }} + - name: "Install poetry" + run: pip install poetry + - name: "Install dependencies" - run: pip install poetry && poetry install + run: poetry install - name: "Build artifacts" run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc9b0c0..457cb4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,9 @@ ## Development Setup -This project uses [poetry][] to manage dependencies and builds, and you will need to install it before working on Decoy. +This project uses [Poetry][] to manage dependencies and builds, and you will need to install it before working on Decoy. -Once poetry is installed, you should be good to set up a virtual environment and install development dependencies. While Decoy is supported on Python >= 3.7, Python >= 3.8 is recommended for development. +Once Poetry is installed, you should be good to set up a virtual environment and install development dependencies. While Decoy supports Python >= 3.6, Python >= 3.8 is recommended for development. ```bash git clone https://github.com/mcous/decoy.git diff --git a/README.md b/README.md index 05a8dbe..7f99922 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,17 @@

Opinionated mocking library for Python

- - + + - - + + - - + + + + +

diff --git a/decoy/__init__.py b/decoy/__init__.py index 06641d6..654e9b8 100644 --- a/decoy/__init__.py +++ b/decoy/__init__.py @@ -1,5 +1,4 @@ """Decoy stubbing and spying library.""" -from __future__ import annotations from typing import Any, Callable, Generic, Optional, cast, overload from . import matchers, errors, warnings @@ -88,7 +87,7 @@ def create_decoy_func( spy = self._core.mock(spec=spec, is_async=is_async) return cast(FuncT, spy) - def when(self, _rehearsal_result: ReturnT) -> Stub[ReturnT]: + def when(self, _rehearsal_result: ReturnT) -> "Stub[ReturnT]": """Create a [Stub][decoy.Stub] configuration using a rehearsal call. See [stubbing usage guide](../usage/when) for more details. diff --git a/decoy/core.py b/decoy/core.py index 76fff8e..6200cbf 100644 --- a/decoy/core.py +++ b/decoy/core.py @@ -1,5 +1,4 @@ """Decoy implementation logic.""" -from __future__ import annotations from typing import Any, Callable, Optional from .spy import SpyConfig, SpyFactory, create_spy as default_create_spy @@ -44,7 +43,7 @@ def mock(self, *, spec: Optional[Any] = None, is_async: bool = False) -> Any: ) return self._create_spy(config) - def when(self, _rehearsal: ReturnT) -> StubCore: + def when(self, _rehearsal: ReturnT) -> "StubCore": """Create a new stub from the last spy rehearsal.""" rehearsal = self._call_stack.consume_when_rehearsal() return StubCore(rehearsal=rehearsal, stub_store=self._stub_store) diff --git a/decoy/spy.py b/decoy/spy.py index 0b023b6..92f974e 100644 --- a/decoy/spy.py +++ b/decoy/spy.py @@ -3,7 +3,6 @@ Classes in this module are heavily inspired by the [unittest.mock library](https://docs.python.org/3/library/unittest.mock.html). """ -from __future__ import annotations from inspect import isclass, iscoroutinefunction, isfunction, signature from functools import partial from typing import get_type_hints, Any, Callable, Dict, NamedTuple, Optional diff --git a/poetry.lock b/poetry.lock index a17dbbb..ff63d97 100644 --- a/poetry.lock +++ b/poetry.lock @@ -50,6 +50,7 @@ python-versions = ">=3.6.2" [package.dependencies] appdirs = "*" click = ">=7.1.2" +dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.8.1,<1" regex = ">=2020.1.8" @@ -91,6 +92,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "dataclasses" +version = "0.8" +description = "A backport of the dataclasses module for Python 3.6" +category = "dev" +optional = false +python-versions = ">=3.6, <3.7" + [[package]] name = "decorator" version = "5.0.9" @@ -551,6 +560,7 @@ python-versions = ">=3.6.1,<4.0.0" [package.dependencies] astunparse = {version = ">=1.6.3,<2.0.0", markers = "python_version < \"3.9\""} cached-property = {version = ">=1.5.2,<2.0.0", markers = "python_version < \"3.8\""} +dataclasses = {version = ">=0.7,<0.9", markers = "python_version == \"3.6\""} typing-extensions = {version = ">=3.7.4.3,<4.0.0.0", markers = "python_version < \"3.8\""} [package.extras] @@ -648,8 +658,8 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "aa7300626cbaed226c243d72ea8d814dcd83274a1d482974aea00e30a95847c9" +python-versions = "^3.6.2" +content-hash = "0b42b7c311b165996429ddc59dbd735f33bf577fe9acfbefed85cdf7b2ec287b" [metadata.files] appdirs = [ @@ -684,6 +694,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +dataclasses = [ + {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, + {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +] decorator = [ {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, diff --git a/pyproject.toml b/pyproject.toml index 36e53cf..c02a633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "decoy" version = "1.6.0" -description = "Opinionated, typed stubbing and verification library for Python" +description = "Opinionated mocking library for Python" authors = ["Mike Cousins "] license = "MIT" readme = "README.md" @@ -18,8 +18,11 @@ classifiers = [ "Typing :: Typed", ] +[tool.poetry.urls] +"Changelog" = "https://github.com/mcous/decoy/releases" + [tool.poetry.dependencies] -python = "^3.7" +python = "^3.6.2" [tool.poetry.dev-dependencies] black = "^21.5b1" @@ -48,5 +51,5 @@ strict = true show_error_codes = true [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry_core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/common.py b/tests/common.py index 8056e73..30eda17 100644 --- a/tests/common.py +++ b/tests/common.py @@ -48,7 +48,7 @@ async def do_the_thing(self, flag: bool) -> None: # NOTE: these `Any`s are forward references for call signature testing purposes -def noop(*args: "Any", **kwargs: "Any") -> "Any": +def noop(*args: Any, **kwargs: Any) -> Any: """No-op.""" pass diff --git a/tests/test_matchers.py b/tests/test_matchers.py index 47f1405..56efb0f 100644 --- a/tests/test_matchers.py +++ b/tests/test_matchers.py @@ -1,14 +1,12 @@ """Matcher tests.""" import pytest from collections import namedtuple -from dataclasses import dataclass from decoy import matchers -from typing import Any, List +from typing import Any, List, NamedTuple from .common import SomeClass -@dataclass -class _HelloClass: +class _HelloClass(NamedTuple): hello: str = "world" @property diff --git a/tests/test_spy.py b/tests/test_spy.py index 3b6064e..f3b4c94 100644 --- a/tests/test_spy.py +++ b/tests/test_spy.py @@ -321,7 +321,22 @@ def test_spy_matches_signature() -> None: assert inspect.signature(func_spy) == inspect.signature(some_func) spy = create_spy(SpyConfig(handle_call=noop)) - assert inspect.signature(spy) == inspect.signature(noop) + + assert inspect.signature(spy) == inspect.Signature( + parameters=( + inspect.Parameter( + name="args", + annotation=Any, + kind=inspect.Parameter.VAR_POSITIONAL, + ), + inspect.Parameter( + name="kwargs", + annotation=Any, + kind=inspect.Parameter.VAR_KEYWORD, + ), + ), + return_annotation=Any, + ) def test_spy_repr() -> None: