Skip to content

Commit 0dfc38c

Browse files
authored
fix(spy): use classmethod __func__ source for async detection (#148)
Closes #146
1 parent 799f511 commit 0dfc38c

File tree

4 files changed

+51
-28
lines changed

4 files changed

+51
-28
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
name: "Continuous integration"
1+
name: 'Continuous integration'
22

33
on: [push, pull_request]
44

55
env:
66
PIP_CACHE_DIR: ${{ github.workspace }}/.cache/pip
77
POETRY_CACHE_DIR: ${{ github.workspace }}/.cache/pypoetry
8-
DEFAULT_PYTHON: "3.8"
8+
DEFAULT_PYTHON: '3.8'
99

1010
jobs:
1111
test:
12-
name: "Test Python ${{ matrix.python-version }} on ${{ matrix.os }}"
12+
name: 'Test Python ${{ matrix.python-version }} on ${{ matrix.os }}'
1313
runs-on: ${{ matrix.os }}-latest
1414
strategy:
1515
matrix:
1616
os: [Ubuntu, Windows, macOS]
17-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
17+
python-version: ['3.7', '3.8', '3.9', '3.10']
1818
steps:
19-
- name: "Check out repository"
19+
- name: 'Check out repository'
2020
uses: actions/checkout@v3
2121

22-
- name: "Set up Python"
22+
- name: 'Set up Python'
2323
uses: actions/setup-python@v4
2424
with:
2525
python-version: ${{ matrix.python-version }}
2626

27-
- name: "Set up dependency cache"
27+
- name: 'Set up dependency cache'
2828
uses: actions/cache@v3
2929
# poetry venv restore is buggy on windows
3030
# https://github.com/python-poetry/poetry/issues/2629
@@ -35,89 +35,89 @@ jobs:
3535
${{ env.PIP_CACHE_DIR }}
3636
${{ env.POETRY_CACHE_DIR }}
3737
38-
- name: "Install poetry"
38+
- name: 'Install poetry'
3939
run: pip install "poetry==1.1.11"
4040

41-
- name: "Install dependencies"
41+
- name: 'Install dependencies'
4242
run: poetry install
4343

44-
- name: "Run tests"
44+
- name: 'Run tests'
4545
run: poetry run coverage run --branch --source=decoy -m pytest --mypy-same-process
4646

47-
- name: "Generate coverage report"
47+
- name: 'Generate coverage report'
4848
run: poetry run coverage xml
4949

50-
- name: "Upload coverage report"
50+
- name: 'Upload coverage report'
5151
uses: codecov/codecov-action@v3
5252

5353
check:
54-
name: "Lint and type checks"
54+
name: 'Lint and type checks'
5555
runs-on: ubuntu-latest
5656
steps:
57-
- name: "Check out repository"
57+
- name: 'Check out repository'
5858
uses: actions/checkout@v3
5959

60-
- name: "Set up Python"
60+
- name: 'Set up Python'
6161
uses: actions/setup-python@v4
6262
with:
6363
python-version: ${{ env.DEFAULT_PYTHON }}
6464

65-
- name: "Set up dependency cache"
65+
- name: 'Set up dependency cache'
6666
uses: actions/cache@v3
6767
with:
6868
key: deps-${{ secrets.GH_CACHE }}-${{ runner.os }}-python${{ env.DEFAULT_PYTHON }}-${{ hashFiles('**/*.lock') }}
6969
path: |
7070
${{ env.PIP_CACHE_DIR }}
7171
${{ env.POETRY_CACHE_DIR }}
7272
73-
- name: "Install poetry"
73+
- name: 'Install poetry'
7474
run: pip install "poetry==1.1.11"
7575

76-
- name: "Install dependencies"
76+
- name: 'Install dependencies'
7777
run: poetry install
7878

79-
- name: "Check formatting"
79+
- name: 'Check formatting'
8080
run: poetry run black --check .
8181

82-
- name: "Check linter"
82+
- name: 'Check linter'
8383
run: poetry run flake8
8484

85-
- name: "Checks types"
85+
- name: 'Checks types'
8686
run: poetry run mypy
8787

8888
build:
8989
name: Build assets and deploy on tags
9090
runs-on: ubuntu-latest
9191
needs: [test, check]
9292
steps:
93-
- name: "Check out repository"
93+
- name: 'Check out repository'
9494
uses: actions/checkout@v3
9595

96-
- name: "Set up Python"
96+
- name: 'Set up Python'
9797
uses: actions/setup-python@v4
9898
with:
9999
python-version: ${{ env.DEFAULT_PYTHON }}
100100

101-
- name: "Set up dependency cache"
101+
- name: 'Set up dependency cache'
102102
uses: actions/cache@v3
103103
with:
104104
key: deps-${{ secrets.GH_CACHE }}-${{ runner.os }}-python${{ env.DEFAULT_PYTHON }}-${{ hashFiles('**/*.lock') }}
105105
path: |
106106
${{ env.PIP_CACHE_DIR }}
107107
${{ env.POETRY_CACHE_DIR }}
108108
109-
- name: "Install poetry"
109+
- name: 'Install poetry'
110110
run: pip install "poetry==1.1.11"
111111

112-
- name: "Install dependencies"
112+
- name: 'Install dependencies'
113113
run: poetry install
114114

115-
- name: "Build artifacts"
115+
- name: 'Build artifacts'
116116
run: |
117117
poetry build
118118
poetry run mkdocs build
119119
120-
- name: "Deploy to PyPI and GitHub Pages"
120+
- name: 'Deploy to PyPI and GitHub Pages'
121121
if: startsWith(github.ref, 'refs/tags/v')
122122
env:
123123
USER_NAME: ${{ github.actor }}

decoy/spy_core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ def create_child_core(self, name: str, is_async: bool) -> "SpyCore":
126126
child_source = child_source.__func__
127127

128128
else:
129+
if isinstance(child_source, classmethod):
130+
child_source = child_source.__func__
131+
129132
child_source = inspect.unwrap(child_source)
130133

131134
if inspect.isfunction(child_source):

tests/fixtures.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ async def bar(self, a: int, b: float, c: str) -> bool:
5353
async def do_the_thing(self, *, flag: bool) -> None:
5454
"""Perform a side-effect without a return value."""
5555

56+
@classmethod
57+
async def async_class_method(cls) -> int:
58+
"""An async class method."""
59+
60+
@staticmethod
61+
async def async_static_method() -> int:
62+
"""An async static method."""
63+
5664

5765
class SomeAsyncCallableClass:
5866
"""Async callable class."""

tests/test_spy_core.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,18 @@ class GetIsAsyncSpec(NamedTuple):
365365
),
366366
expected_is_async=True,
367367
),
368+
GetIsAsyncSpec(
369+
subject=SpyCore(source=SomeAsyncClass, name=None).create_child_core(
370+
"async_class_method", is_async=False
371+
),
372+
expected_is_async=True,
373+
),
374+
GetIsAsyncSpec(
375+
subject=SpyCore(source=SomeAsyncClass, name=None).create_child_core(
376+
"async_static_method", is_async=False
377+
),
378+
expected_is_async=True,
379+
),
368380
],
369381
)
370382
def test_get_is_async(subject: SpyCore, expected_is_async: bool) -> None:

0 commit comments

Comments
 (0)