async-solipsism 0.5__tar.gz → 0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- async_solipsism-0.7/.github/workflows/test.yml +36 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/.gitignore +1 -0
- async_solipsism-0.7/.pre-commit-config.yaml +12 -0
- {async-solipsism-0.5/src/async_solipsism.egg-info → async_solipsism-0.7}/PKG-INFO +57 -15
- async-solipsism-0.5/PKG-INFO → async_solipsism-0.7/README.md +47 -24
- async_solipsism-0.7/pyproject.toml +35 -0
- async_solipsism-0.7/requirements.in +4 -0
- async_solipsism-0.7/requirements.txt +46 -0
- async_solipsism-0.7/setup.cfg +4 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/__init__.py +2 -2
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/loop.py +42 -2
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/socket.py +28 -14
- async-solipsism-0.5/README.md → async_solipsism-0.7/src/async_solipsism.egg-info/PKG-INFO +66 -8
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism.egg-info/SOURCES.txt +2 -1
- async_solipsism-0.7/src/async_solipsism.egg-info/requires.txt +6 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/test/test_loop.py +31 -17
- {async-solipsism-0.5 → async_solipsism-0.7}/test/test_socket.py +41 -1
- async-solipsism-0.5/.github/workflows/test.yml +0 -25
- async-solipsism-0.5/pyproject.toml +0 -2
- async-solipsism-0.5/requirements.txt +0 -4
- async-solipsism-0.5/setup.cfg +0 -34
- async-solipsism-0.5/src/async_solipsism.egg-info/requires.txt +0 -5
- {async-solipsism-0.5 → async_solipsism-0.7}/.flake8 +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/LICENSE +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/pytest.ini +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/setup.py +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/clock.py +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/exceptions.py +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism/selector.py +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism.egg-info/dependency_links.txt +0 -0
- {async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
lint:
|
|
7
|
+
runs-on: ubuntu-22.04
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- name: Set up Python 3.8
|
|
11
|
+
uses: actions/setup-python@v5
|
|
12
|
+
with:
|
|
13
|
+
python-version: "3.8"
|
|
14
|
+
- name: Install dependencies
|
|
15
|
+
run: pip install -r requirements.txt
|
|
16
|
+
- name: Run pre-commit
|
|
17
|
+
run: pre-commit run --all-files
|
|
18
|
+
|
|
19
|
+
build:
|
|
20
|
+
runs-on: ubuntu-22.04
|
|
21
|
+
strategy:
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
allow-prereleases: true
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: pip install -r requirements.txt
|
|
33
|
+
- name: Install package
|
|
34
|
+
run: pip install .
|
|
35
|
+
- name: Run pytest
|
|
36
|
+
run: pytest
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pycqa/flake8
|
|
3
|
+
rev: "7.0.0"
|
|
4
|
+
hooks:
|
|
5
|
+
- id: flake8
|
|
6
|
+
- repo: https://github.com/jazzband/pip-tools
|
|
7
|
+
rev: 7.4.1
|
|
8
|
+
hooks:
|
|
9
|
+
- id: pip-compile
|
|
10
|
+
language_version: python3.8
|
|
11
|
+
args: ["--output-file=requirements.txt", "--extra=test", "--strip-extras", "pyproject.toml"]
|
|
12
|
+
files: '^(?:pyproject\.toml|requirements\.txt)$'
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: async-solipsism
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7
|
|
4
4
|
Summary: Asyncio event loop that doesn't interact with the outside world
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Author-email: Bruce Merry <bmerry@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/bmerry/async-solipsism
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
8
|
Classifier: Framework :: AsyncIO
|
|
10
9
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
10
|
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
12
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.8
|
|
13
12
|
Description-Content-Type: text/markdown
|
|
14
|
-
Provides-Extra: test
|
|
15
13
|
License-File: LICENSE
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pre-commit; extra == "test"
|
|
16
|
+
Requires-Dist: pytest; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
16
19
|
|
|
17
20
|
# A solipsist event loop
|
|
18
21
|
|
|
19
|
-
async-solipsism
|
|
22
|
+
async-solipsism provides a Python asyncio event loop that does not interact with
|
|
20
23
|
the outside world at all. This is ideal for writing unit tests that intend to
|
|
21
24
|
mock out real-world interactions. It makes for tests that are reliable
|
|
22
25
|
(unaffected by network outages), reproducible (not affected by random timing
|
|
@@ -107,8 +110,17 @@ port number of 0, and async-solipsism will bind the first port number above
|
|
|
107
110
|
### Integration with pytest-asyncio
|
|
108
111
|
|
|
109
112
|
async-solipsism and pytest-asyncio complement each other well: just write a
|
|
110
|
-
custom `
|
|
111
|
-
override the default provided by pytest-asyncio:
|
|
113
|
+
custom `event_loop_policy` fixture in your test file or `conftest.py` and it
|
|
114
|
+
will override the default provided by pytest-asyncio:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
@pytest.fixture
|
|
118
|
+
def event_loop_policy():
|
|
119
|
+
return async_solipsism.EventLoopPolicy()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The above is for pytest-asyncio 0.23+. If you're using an older version, then
|
|
123
|
+
instead you should do this:
|
|
112
124
|
|
|
113
125
|
```python
|
|
114
126
|
@pytest.fixture
|
|
@@ -118,7 +130,14 @@ def event_loop():
|
|
|
118
130
|
loop.close()
|
|
119
131
|
```
|
|
120
132
|
|
|
121
|
-
### Integration with
|
|
133
|
+
### Integration with aiohappyeyeballs
|
|
134
|
+
|
|
135
|
+
Unfortunately `aiohappyeyeballs.start_connection` doesn't work out of the box
|
|
136
|
+
because it relies on creating a real socket. I've created a replacement
|
|
137
|
+
`async_solipsism.aiohappyeyeballs_start_connection` that can be used to
|
|
138
|
+
replace it. See the aiohttp section below for example code.
|
|
139
|
+
|
|
140
|
+
### Integration with aiohttp
|
|
122
141
|
|
|
123
142
|
A little extra work is required to work with aiohttp's test utilities, but it
|
|
124
143
|
is possible. The example below requires at least aiohttp 3.8.0.
|
|
@@ -130,10 +149,14 @@ from aiohttp import web, test_utils
|
|
|
130
149
|
|
|
131
150
|
|
|
132
151
|
@pytest.fixture
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
152
|
+
def event_loop_policy():
|
|
153
|
+
return async_solipsism.EventLoopPolicy()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@pytest.fixture(autouse=True)
|
|
157
|
+
def mock_start_connection(monkeypatch):
|
|
158
|
+
monkeypatch.setattr("aiohappyeyeballs.start_connection",
|
|
159
|
+
async_solipsism.aiohappyeyeballs_start_connection)
|
|
137
160
|
|
|
138
161
|
|
|
139
162
|
def socket_factory(host, port, family):
|
|
@@ -183,6 +206,25 @@ Calling functions that are not supported will generally raise
|
|
|
183
206
|
|
|
184
207
|
## Changelog
|
|
185
208
|
|
|
209
|
+
### 0.7
|
|
210
|
+
|
|
211
|
+
- Add a replacement function for `aiohappyeyeballs.start_connection`.
|
|
212
|
+
|
|
213
|
+
### 0.6
|
|
214
|
+
|
|
215
|
+
- Drop support for Python 3.6 and 3.7, which have reached end of life.
|
|
216
|
+
- Add `EventLoopPolicy` to simplify integration with pytest-asyncio 0.23+,
|
|
217
|
+
and use it for the internal tests.
|
|
218
|
+
- Make `Socket.write` and `Socket.recv_into` accept arbitrary buffer-protocol
|
|
219
|
+
objects.
|
|
220
|
+
- Various packaging and developer experience improvements:
|
|
221
|
+
- Put the packaging metadata in pyproject.toml, removing setup.cfg.
|
|
222
|
+
- Pin versions for CI in requirements.txt using pip-compile.
|
|
223
|
+
- Use pre-commit to enforce flake8 and pip-compile.
|
|
224
|
+
- Remove wheel from `build-system.requires` (on setuptools advice).
|
|
225
|
+
- Test against Python 3.11 and 3.12.
|
|
226
|
+
- Run CI workflow against pull requests.
|
|
227
|
+
|
|
186
228
|
### 0.5
|
|
187
229
|
|
|
188
230
|
- Map port 0 to an unused port.
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: async-solipsism
|
|
3
|
-
Version: 0.5
|
|
4
|
-
Summary: Asyncio event loop that doesn't interact with the outside world
|
|
5
|
-
Home-page: https://github.com/bmerry/async-solipsism
|
|
6
|
-
Author: Bruce Merry
|
|
7
|
-
Author-email: bmerry@gmail.com
|
|
8
|
-
Classifier: Development Status :: 3 - Alpha
|
|
9
|
-
Classifier: Framework :: AsyncIO
|
|
10
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
|
-
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
12
|
-
Requires-Python: >=3.6
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
Provides-Extra: test
|
|
15
|
-
License-File: LICENSE
|
|
16
|
-
|
|
17
1
|
# A solipsist event loop
|
|
18
2
|
|
|
19
|
-
async-solipsism
|
|
3
|
+
async-solipsism provides a Python asyncio event loop that does not interact with
|
|
20
4
|
the outside world at all. This is ideal for writing unit tests that intend to
|
|
21
5
|
mock out real-world interactions. It makes for tests that are reliable
|
|
22
6
|
(unaffected by network outages), reproducible (not affected by random timing
|
|
@@ -107,8 +91,17 @@ port number of 0, and async-solipsism will bind the first port number above
|
|
|
107
91
|
### Integration with pytest-asyncio
|
|
108
92
|
|
|
109
93
|
async-solipsism and pytest-asyncio complement each other well: just write a
|
|
110
|
-
custom `
|
|
111
|
-
override the default provided by pytest-asyncio:
|
|
94
|
+
custom `event_loop_policy` fixture in your test file or `conftest.py` and it
|
|
95
|
+
will override the default provided by pytest-asyncio:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
@pytest.fixture
|
|
99
|
+
def event_loop_policy():
|
|
100
|
+
return async_solipsism.EventLoopPolicy()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The above is for pytest-asyncio 0.23+. If you're using an older version, then
|
|
104
|
+
instead you should do this:
|
|
112
105
|
|
|
113
106
|
```python
|
|
114
107
|
@pytest.fixture
|
|
@@ -118,7 +111,14 @@ def event_loop():
|
|
|
118
111
|
loop.close()
|
|
119
112
|
```
|
|
120
113
|
|
|
121
|
-
### Integration with
|
|
114
|
+
### Integration with aiohappyeyeballs
|
|
115
|
+
|
|
116
|
+
Unfortunately `aiohappyeyeballs.start_connection` doesn't work out of the box
|
|
117
|
+
because it relies on creating a real socket. I've created a replacement
|
|
118
|
+
`async_solipsism.aiohappyeyeballs_start_connection` that can be used to
|
|
119
|
+
replace it. See the aiohttp section below for example code.
|
|
120
|
+
|
|
121
|
+
### Integration with aiohttp
|
|
122
122
|
|
|
123
123
|
A little extra work is required to work with aiohttp's test utilities, but it
|
|
124
124
|
is possible. The example below requires at least aiohttp 3.8.0.
|
|
@@ -130,10 +130,14 @@ from aiohttp import web, test_utils
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
@pytest.fixture
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
def event_loop_policy():
|
|
134
|
+
return async_solipsism.EventLoopPolicy()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@pytest.fixture(autouse=True)
|
|
138
|
+
def mock_start_connection(monkeypatch):
|
|
139
|
+
monkeypatch.setattr("aiohappyeyeballs.start_connection",
|
|
140
|
+
async_solipsism.aiohappyeyeballs_start_connection)
|
|
137
141
|
|
|
138
142
|
|
|
139
143
|
def socket_factory(host, port, family):
|
|
@@ -183,6 +187,25 @@ Calling functions that are not supported will generally raise
|
|
|
183
187
|
|
|
184
188
|
## Changelog
|
|
185
189
|
|
|
190
|
+
### 0.7
|
|
191
|
+
|
|
192
|
+
- Add a replacement function for `aiohappyeyeballs.start_connection`.
|
|
193
|
+
|
|
194
|
+
### 0.6
|
|
195
|
+
|
|
196
|
+
- Drop support for Python 3.6 and 3.7, which have reached end of life.
|
|
197
|
+
- Add `EventLoopPolicy` to simplify integration with pytest-asyncio 0.23+,
|
|
198
|
+
and use it for the internal tests.
|
|
199
|
+
- Make `Socket.write` and `Socket.recv_into` accept arbitrary buffer-protocol
|
|
200
|
+
objects.
|
|
201
|
+
- Various packaging and developer experience improvements:
|
|
202
|
+
- Put the packaging metadata in pyproject.toml, removing setup.cfg.
|
|
203
|
+
- Pin versions for CI in requirements.txt using pip-compile.
|
|
204
|
+
- Use pre-commit to enforce flake8 and pip-compile.
|
|
205
|
+
- Remove wheel from `build-system.requires` (on setuptools advice).
|
|
206
|
+
- Test against Python 3.11 and 3.12.
|
|
207
|
+
- Run CI workflow against pull requests.
|
|
208
|
+
|
|
186
209
|
### 0.5
|
|
187
210
|
|
|
188
211
|
- Map port 0 to an unused port.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "setuptools-scm>=8.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "async-solipsism"
|
|
7
|
+
dynamic = ["version", "readme"]
|
|
8
|
+
description = "Asyncio event loop that doesn't interact with the outside world"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Bruce Merry", email = "bmerry@gmail.com"}
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
|
+
"Framework :: AsyncIO",
|
|
16
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
17
|
+
"Topic :: Software Development :: Testing :: Mocking",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/bmerry/async-solipsism"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
test = [
|
|
25
|
+
"pre-commit",
|
|
26
|
+
"pytest",
|
|
27
|
+
"pytest-asyncio>=0.23",
|
|
28
|
+
"pytest-mock",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools_scm]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.dynamic]
|
|
34
|
+
version = {attr = "async_solipsism.__version__"}
|
|
35
|
+
readme = {file = ["README.md"], content-type = "text/markdown"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.8
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --extra=test --output-file=requirements.txt --strip-extras pyproject.toml
|
|
6
|
+
#
|
|
7
|
+
cfgv==3.4.0
|
|
8
|
+
# via pre-commit
|
|
9
|
+
distlib==0.3.8
|
|
10
|
+
# via virtualenv
|
|
11
|
+
exceptiongroup==1.2.0
|
|
12
|
+
# via pytest
|
|
13
|
+
filelock==3.13.1
|
|
14
|
+
# via virtualenv
|
|
15
|
+
identify==2.5.35
|
|
16
|
+
# via pre-commit
|
|
17
|
+
iniconfig==2.0.0
|
|
18
|
+
# via pytest
|
|
19
|
+
nodeenv==1.8.0
|
|
20
|
+
# via pre-commit
|
|
21
|
+
packaging==24.0
|
|
22
|
+
# via pytest
|
|
23
|
+
platformdirs==4.2.0
|
|
24
|
+
# via virtualenv
|
|
25
|
+
pluggy==1.4.0
|
|
26
|
+
# via pytest
|
|
27
|
+
pre-commit==3.5.0
|
|
28
|
+
# via async-solipsism (pyproject.toml)
|
|
29
|
+
pytest==8.1.1
|
|
30
|
+
# via
|
|
31
|
+
# async-solipsism (pyproject.toml)
|
|
32
|
+
# pytest-asyncio
|
|
33
|
+
# pytest-mock
|
|
34
|
+
pytest-asyncio==0.23.6
|
|
35
|
+
# via async-solipsism (pyproject.toml)
|
|
36
|
+
pytest-mock==3.14.0
|
|
37
|
+
# via async-solipsism (pyproject.toml)
|
|
38
|
+
pyyaml==6.0.1
|
|
39
|
+
# via pre-commit
|
|
40
|
+
tomli==2.0.1
|
|
41
|
+
# via pytest
|
|
42
|
+
virtualenv==20.25.1
|
|
43
|
+
# via pre-commit
|
|
44
|
+
|
|
45
|
+
# The following packages are considered to be unsafe in a requirements file:
|
|
46
|
+
# setuptools
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2020-2022 Bruce Merry
|
|
1
|
+
# Copyright 2020-2022, 2024 Bruce Merry
|
|
2
2
|
#
|
|
3
3
|
# This file is part of async-solipsism.
|
|
4
4
|
#
|
|
@@ -19,4 +19,4 @@ from .exceptions import * # noqa
|
|
|
19
19
|
from .socket import * # noqa
|
|
20
20
|
from .loop import * # noqa
|
|
21
21
|
|
|
22
|
-
__version__ = '0.
|
|
22
|
+
__version__ = '0.7'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2020, 2022 Bruce Merry
|
|
1
|
+
# Copyright 2020, 2022, 2024 Bruce Merry
|
|
2
2
|
#
|
|
3
3
|
# This file is part of async-solipsism.
|
|
4
4
|
#
|
|
@@ -24,7 +24,7 @@ from . import selector, socket as _socket
|
|
|
24
24
|
from .exceptions import SolipsismError
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
__all__ = ('EventLoop', 'stream_pairs')
|
|
27
|
+
__all__ = ('EventLoop', 'EventLoopPolicy', 'aiohappyeyeballs_start_connection', 'stream_pairs')
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class EventLoop(asyncio.selector_events.BaseSelectorEventLoop):
|
|
@@ -133,6 +133,27 @@ class EventLoop(asyncio.selector_events.BaseSelectorEventLoop):
|
|
|
133
133
|
**kwargs
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
+
async def aiohappyeyeballs_start_connection(self,
|
|
137
|
+
addr_infos,
|
|
138
|
+
*,
|
|
139
|
+
local_addr_infos=None,
|
|
140
|
+
happy_eyeballs_delay=None,
|
|
141
|
+
interleave=None):
|
|
142
|
+
for addr_info in addr_infos:
|
|
143
|
+
addr = addr_info[4]
|
|
144
|
+
addr = _socket._normalise_ipv6_sockaddr(addr)
|
|
145
|
+
if addr in self.__listening_sockets:
|
|
146
|
+
listener = self.__listening_sockets[addr]
|
|
147
|
+
if local_addr_infos:
|
|
148
|
+
local_addr_info = local_addr_infos[0]
|
|
149
|
+
else:
|
|
150
|
+
port = self.__next_port
|
|
151
|
+
self.__next_port += 1
|
|
152
|
+
local_addr_info = ('::1', port, 0, 0)
|
|
153
|
+
sock = await listener.make_connection(local_addr_info)
|
|
154
|
+
return sock
|
|
155
|
+
raise ConnectionRefusedError('No socket listening on requested addresses')
|
|
156
|
+
|
|
136
157
|
async def connect_read_pipe(self, protocol_factory, pipe):
|
|
137
158
|
raise SolipsismError("connect_read_pipe is not supported")
|
|
138
159
|
|
|
@@ -197,8 +218,27 @@ class EventLoop(asyncio.selector_events.BaseSelectorEventLoop):
|
|
|
197
218
|
super()._stop_serving(sock)
|
|
198
219
|
|
|
199
220
|
|
|
221
|
+
class EventLoopPolicy(asyncio.DefaultEventLoopPolicy):
|
|
222
|
+
def new_event_loop(self) -> EventLoop:
|
|
223
|
+
return EventLoop()
|
|
224
|
+
|
|
225
|
+
|
|
200
226
|
async def stream_pairs(capacity=None):
|
|
201
227
|
sock1, sock2 = _socket.socketpair(capacity=capacity)
|
|
202
228
|
streams1 = await asyncio.open_connection(sock=sock1)
|
|
203
229
|
streams2 = await asyncio.open_connection(sock=sock2)
|
|
204
230
|
return streams1, streams2
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def aiohappyeyeballs_start_connection(addr_infos,
|
|
234
|
+
*,
|
|
235
|
+
local_addr_infos=None,
|
|
236
|
+
happy_eyeballs_delay=None,
|
|
237
|
+
interleave=None,
|
|
238
|
+
loop=None):
|
|
239
|
+
if loop is None:
|
|
240
|
+
loop = asyncio.get_running_loop()
|
|
241
|
+
return await loop.aiohappyeyeballs_start_connection(addr_infos,
|
|
242
|
+
local_addr_infos=local_addr_infos,
|
|
243
|
+
happy_eyeballs_delay=happy_eyeballs_delay,
|
|
244
|
+
interleave=interleave)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2020 Bruce Merry
|
|
1
|
+
# Copyright 2020, 2024 Bruce Merry
|
|
2
2
|
#
|
|
3
3
|
# This file is part of async-solipsism.
|
|
4
4
|
#
|
|
@@ -30,6 +30,11 @@ DEFAULT_CAPACITY = 65536
|
|
|
30
30
|
__all__ = ('Socket', 'ListenSocket', 'Queue', 'socketpair')
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _view(bytes):
|
|
34
|
+
"""Get a 1D byte-typed memoryview of an object implementing the buffer protocol."""
|
|
35
|
+
return memoryview(bytes).cast("B")
|
|
36
|
+
|
|
37
|
+
|
|
33
38
|
class Queue:
|
|
34
39
|
def __init__(self, capacity=None):
|
|
35
40
|
self.capacity = capacity or DEFAULT_CAPACITY
|
|
@@ -50,11 +55,14 @@ class Queue:
|
|
|
50
55
|
raise BrokenPipeError(errno.EPIPE, 'Broken pipe')
|
|
51
56
|
if len(self) >= self.capacity:
|
|
52
57
|
return None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
n
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
with _view(data) as view:
|
|
59
|
+
n = len(view)
|
|
60
|
+
if len(self) + n > self.capacity:
|
|
61
|
+
n = self.capacity - len(self)
|
|
62
|
+
with view[:n] as prefix:
|
|
63
|
+
self._buffer += prefix
|
|
64
|
+
else:
|
|
65
|
+
self._buffer += view
|
|
58
66
|
return n
|
|
59
67
|
|
|
60
68
|
def read(self, size=-1):
|
|
@@ -68,7 +76,8 @@ class Queue:
|
|
|
68
76
|
self._buffer = bytearray()
|
|
69
77
|
else:
|
|
70
78
|
n = min(size, len(self._buffer))
|
|
71
|
-
|
|
79
|
+
with memoryview(self._buffer) as view, view[:n] as prefix:
|
|
80
|
+
ret = bytes(prefix)
|
|
72
81
|
self._buffer = self._buffer[n:]
|
|
73
82
|
return ret
|
|
74
83
|
|
|
@@ -93,7 +102,7 @@ class SocketFd:
|
|
|
93
102
|
return hash(self.socket)
|
|
94
103
|
|
|
95
104
|
def __eq__(self, other):
|
|
96
|
-
if type(other)
|
|
105
|
+
if type(other) is SocketFd:
|
|
97
106
|
return self.socket is other.socket
|
|
98
107
|
return NotImplemented
|
|
99
108
|
|
|
@@ -111,7 +120,7 @@ class _SocketBase:
|
|
|
111
120
|
|
|
112
121
|
def setblocking(self, flag):
|
|
113
122
|
if flag:
|
|
114
|
-
raise SolipsismError('Socket only
|
|
123
|
+
raise SolipsismError('Socket only supports non-blocking operation')
|
|
115
124
|
|
|
116
125
|
def setsockopt(self, level, optname, value, optlen=None):
|
|
117
126
|
key = (level, optname)
|
|
@@ -164,11 +173,16 @@ class Socket(_SocketBase):
|
|
|
164
173
|
|
|
165
174
|
def recv_into(self, buffer, nbytes=0, flags=0):
|
|
166
175
|
# TODO: implement more efficiently?
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
with _view(buffer) as view:
|
|
177
|
+
if not nbytes:
|
|
178
|
+
nbytes = len(view)
|
|
179
|
+
if nbytes < 0:
|
|
180
|
+
raise ValueError("negative buffersize in recv_into")
|
|
181
|
+
if nbytes > len(view):
|
|
182
|
+
raise ValueError("buffer too small for requested bytes")
|
|
183
|
+
data = self.recv(nbytes)
|
|
184
|
+
view[:len(data)] = data
|
|
185
|
+
return len(data)
|
|
172
186
|
|
|
173
187
|
def send(self, bytes, flags=0):
|
|
174
188
|
self._check_closed()
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: async-solipsism
|
|
3
|
+
Version: 0.7
|
|
4
|
+
Summary: Asyncio event loop that doesn't interact with the outside world
|
|
5
|
+
Author-email: Bruce Merry <bmerry@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/bmerry/async-solipsism
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
|
+
Classifier: Framework :: AsyncIO
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
10
|
+
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pre-commit; extra == "test"
|
|
16
|
+
Requires-Dist: pytest; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
19
|
+
|
|
1
20
|
# A solipsist event loop
|
|
2
21
|
|
|
3
|
-
async-solipsism
|
|
22
|
+
async-solipsism provides a Python asyncio event loop that does not interact with
|
|
4
23
|
the outside world at all. This is ideal for writing unit tests that intend to
|
|
5
24
|
mock out real-world interactions. It makes for tests that are reliable
|
|
6
25
|
(unaffected by network outages), reproducible (not affected by random timing
|
|
@@ -91,8 +110,17 @@ port number of 0, and async-solipsism will bind the first port number above
|
|
|
91
110
|
### Integration with pytest-asyncio
|
|
92
111
|
|
|
93
112
|
async-solipsism and pytest-asyncio complement each other well: just write a
|
|
94
|
-
custom `
|
|
95
|
-
override the default provided by pytest-asyncio:
|
|
113
|
+
custom `event_loop_policy` fixture in your test file or `conftest.py` and it
|
|
114
|
+
will override the default provided by pytest-asyncio:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
@pytest.fixture
|
|
118
|
+
def event_loop_policy():
|
|
119
|
+
return async_solipsism.EventLoopPolicy()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The above is for pytest-asyncio 0.23+. If you're using an older version, then
|
|
123
|
+
instead you should do this:
|
|
96
124
|
|
|
97
125
|
```python
|
|
98
126
|
@pytest.fixture
|
|
@@ -102,7 +130,14 @@ def event_loop():
|
|
|
102
130
|
loop.close()
|
|
103
131
|
```
|
|
104
132
|
|
|
105
|
-
### Integration with
|
|
133
|
+
### Integration with aiohappyeyeballs
|
|
134
|
+
|
|
135
|
+
Unfortunately `aiohappyeyeballs.start_connection` doesn't work out of the box
|
|
136
|
+
because it relies on creating a real socket. I've created a replacement
|
|
137
|
+
`async_solipsism.aiohappyeyeballs_start_connection` that can be used to
|
|
138
|
+
replace it. See the aiohttp section below for example code.
|
|
139
|
+
|
|
140
|
+
### Integration with aiohttp
|
|
106
141
|
|
|
107
142
|
A little extra work is required to work with aiohttp's test utilities, but it
|
|
108
143
|
is possible. The example below requires at least aiohttp 3.8.0.
|
|
@@ -114,10 +149,14 @@ from aiohttp import web, test_utils
|
|
|
114
149
|
|
|
115
150
|
|
|
116
151
|
@pytest.fixture
|
|
117
|
-
def
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
def event_loop_policy():
|
|
153
|
+
return async_solipsism.EventLoopPolicy()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@pytest.fixture(autouse=True)
|
|
157
|
+
def mock_start_connection(monkeypatch):
|
|
158
|
+
monkeypatch.setattr("aiohappyeyeballs.start_connection",
|
|
159
|
+
async_solipsism.aiohappyeyeballs_start_connection)
|
|
121
160
|
|
|
122
161
|
|
|
123
162
|
def socket_factory(host, port, family):
|
|
@@ -167,6 +206,25 @@ Calling functions that are not supported will generally raise
|
|
|
167
206
|
|
|
168
207
|
## Changelog
|
|
169
208
|
|
|
209
|
+
### 0.7
|
|
210
|
+
|
|
211
|
+
- Add a replacement function for `aiohappyeyeballs.start_connection`.
|
|
212
|
+
|
|
213
|
+
### 0.6
|
|
214
|
+
|
|
215
|
+
- Drop support for Python 3.6 and 3.7, which have reached end of life.
|
|
216
|
+
- Add `EventLoopPolicy` to simplify integration with pytest-asyncio 0.23+,
|
|
217
|
+
and use it for the internal tests.
|
|
218
|
+
- Make `Socket.write` and `Socket.recv_into` accept arbitrary buffer-protocol
|
|
219
|
+
objects.
|
|
220
|
+
- Various packaging and developer experience improvements:
|
|
221
|
+
- Put the packaging metadata in pyproject.toml, removing setup.cfg.
|
|
222
|
+
- Pin versions for CI in requirements.txt using pip-compile.
|
|
223
|
+
- Use pre-commit to enforce flake8 and pip-compile.
|
|
224
|
+
- Remove wheel from `build-system.requires` (on setuptools advice).
|
|
225
|
+
- Test against Python 3.11 and 3.12.
|
|
226
|
+
- Run CI workflow against pull requests.
|
|
227
|
+
|
|
170
228
|
### 0.5
|
|
171
229
|
|
|
172
230
|
- Map port 0 to an unused port.
|
|
@@ -25,13 +25,12 @@ import async_solipsism
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@pytest.fixture
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
yield loop
|
|
31
|
-
loop.close()
|
|
28
|
+
def event_loop_policy():
|
|
29
|
+
return async_solipsism.EventLoopPolicy()
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
async def test_sleep(
|
|
32
|
+
async def test_sleep():
|
|
33
|
+
event_loop = asyncio.get_running_loop()
|
|
35
34
|
assert event_loop.time() == 0.0
|
|
36
35
|
await asyncio.sleep(2)
|
|
37
36
|
assert event_loop.time() == 2.0
|
|
@@ -53,7 +52,9 @@ def test_sleep_forever(event_loop):
|
|
|
53
52
|
]
|
|
54
53
|
)
|
|
55
54
|
@pytest.mark.parametrize('delay', [False, True])
|
|
56
|
-
async def test_delayed_sock_recv(method, delay
|
|
55
|
+
async def test_delayed_sock_recv(method, delay):
|
|
56
|
+
event_loop = asyncio.get_running_loop()
|
|
57
|
+
|
|
57
58
|
async def delayed_write(wsock):
|
|
58
59
|
await asyncio.sleep(1)
|
|
59
60
|
wsock.send(b'Hello')
|
|
@@ -76,7 +77,9 @@ async def test_delayed_sock_recv(method, delay, event_loop):
|
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
@pytest.mark.parametrize('size', [10, 10**7])
|
|
79
|
-
async def test_sock_sendall(size
|
|
80
|
+
async def test_sock_sendall(size):
|
|
81
|
+
event_loop = asyncio.get_running_loop()
|
|
82
|
+
|
|
80
83
|
async def delayed_read(rsock):
|
|
81
84
|
n = 0
|
|
82
85
|
while True:
|
|
@@ -99,7 +102,8 @@ async def test_sock_sendall(size, event_loop):
|
|
|
99
102
|
assert n == size
|
|
100
103
|
|
|
101
104
|
|
|
102
|
-
async def test_connect_existing(
|
|
105
|
+
async def test_connect_existing(mocker):
|
|
106
|
+
event_loop = asyncio.get_running_loop()
|
|
103
107
|
sock1, sock2 = async_solipsism.socketpair()
|
|
104
108
|
transport1, protocol1 = await event_loop.connect_accepted_socket(
|
|
105
109
|
mocker.MagicMock, sock1)
|
|
@@ -128,7 +132,9 @@ async def test_stream():
|
|
|
128
132
|
|
|
129
133
|
|
|
130
134
|
@pytest.mark.parametrize('manual_socket', [False, True])
|
|
131
|
-
async def test_server(
|
|
135
|
+
async def test_server(manual_socket):
|
|
136
|
+
event_loop = asyncio.get_running_loop()
|
|
137
|
+
|
|
132
138
|
def callback(reader, writer):
|
|
133
139
|
server_conn.set_result((reader, writer))
|
|
134
140
|
|
|
@@ -156,7 +162,7 @@ async def test_server(event_loop, manual_socket):
|
|
|
156
162
|
await server.wait_closed()
|
|
157
163
|
|
|
158
164
|
|
|
159
|
-
async def test_unused_port(
|
|
165
|
+
async def test_unused_port():
|
|
160
166
|
def callback(reader, writer):
|
|
161
167
|
pass
|
|
162
168
|
|
|
@@ -172,7 +178,8 @@ async def test_unused_port(event_loop):
|
|
|
172
178
|
await server2.wait_closed()
|
|
173
179
|
|
|
174
180
|
|
|
175
|
-
async def test_close_server(
|
|
181
|
+
async def test_close_server():
|
|
182
|
+
event_loop = asyncio.get_running_loop()
|
|
176
183
|
server = await asyncio.start_server(lambda reader, writer: None, 'test.invalid', 1234)
|
|
177
184
|
server.close()
|
|
178
185
|
await server.wait_closed()
|
|
@@ -180,18 +187,21 @@ async def test_close_server(event_loop):
|
|
|
180
187
|
await event_loop.create_connection('test.invalid', 1234)
|
|
181
188
|
|
|
182
189
|
|
|
183
|
-
async def test_create_connection_no_listener(
|
|
190
|
+
async def test_create_connection_no_listener():
|
|
191
|
+
event_loop = asyncio.get_running_loop()
|
|
184
192
|
with pytest.raises(ConnectionRefusedError):
|
|
185
193
|
await event_loop.create_connection('test.invalid', 1234)
|
|
186
194
|
|
|
187
195
|
|
|
188
|
-
async def test_run_in_executor_implicit(
|
|
196
|
+
async def test_run_in_executor_implicit():
|
|
197
|
+
event_loop = asyncio.get_running_loop()
|
|
189
198
|
thread_id = await event_loop.run_in_executor(None, threading.get_ident)
|
|
190
199
|
assert isinstance(thread_id, int)
|
|
191
200
|
assert thread_id != threading.get_ident()
|
|
192
201
|
|
|
193
202
|
|
|
194
|
-
async def test_run_in_executor_explicit(
|
|
203
|
+
async def test_run_in_executor_explicit():
|
|
204
|
+
event_loop = asyncio.get_running_loop()
|
|
195
205
|
my_executor = concurrent.futures.ThreadPoolExecutor(1)
|
|
196
206
|
expected_thread_id = my_executor.submit(threading.get_ident).result()
|
|
197
207
|
assert isinstance(expected_thread_id, int)
|
|
@@ -201,7 +211,8 @@ async def test_run_in_executor_explicit(event_loop):
|
|
|
201
211
|
|
|
202
212
|
|
|
203
213
|
@pytest.mark.skipif("sys.version_info < (3, 7)")
|
|
204
|
-
async def test_sendfile(
|
|
214
|
+
async def test_sendfile(tmp_path):
|
|
215
|
+
event_loop = asyncio.get_running_loop()
|
|
205
216
|
tmp_file = tmp_path / 'test_sendfile.txt'
|
|
206
217
|
tmp_file.write_bytes(b'Hello world\n')
|
|
207
218
|
((reader1, writer1), (reader2, writer2)) = await async_solipsism.stream_pairs()
|
|
@@ -215,14 +226,17 @@ async def test_sendfile(event_loop, tmp_path):
|
|
|
215
226
|
await writer2.wait_closed()
|
|
216
227
|
|
|
217
228
|
|
|
218
|
-
async def test_call_soon_threadsafe(
|
|
229
|
+
async def test_call_soon_threadsafe():
|
|
230
|
+
event_loop = asyncio.get_running_loop()
|
|
219
231
|
future = event_loop.create_future()
|
|
220
232
|
event_loop.call_soon_threadsafe(future.set_result, 3)
|
|
221
233
|
result = await future
|
|
222
234
|
assert result == 3
|
|
223
235
|
|
|
224
236
|
|
|
225
|
-
async def test_call_soon_threadsafe_wrong_thread(
|
|
237
|
+
async def test_call_soon_threadsafe_wrong_thread():
|
|
238
|
+
event_loop = asyncio.get_running_loop()
|
|
239
|
+
|
|
226
240
|
def thread_func():
|
|
227
241
|
event_loop.call_soon_threadsafe(lambda: None)
|
|
228
242
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2020 Bruce Merry
|
|
1
|
+
# Copyright 2020, 2024 Bruce Merry
|
|
2
2
|
#
|
|
3
3
|
# This file is part of async-solipsism.
|
|
4
4
|
#
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
# You should have received a copy of the GNU General Public License
|
|
16
16
|
# along with async-solipsism. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
+
import array
|
|
18
19
|
import socket
|
|
20
|
+
import struct
|
|
19
21
|
|
|
20
22
|
import pytest
|
|
21
23
|
|
|
@@ -69,3 +71,41 @@ def test_use_after_shutdown(end):
|
|
|
69
71
|
assert sock0.recv(1) == b''
|
|
70
72
|
with pytest.raises(BrokenPipeError):
|
|
71
73
|
sock1.send(b'hello')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_recv_into_array():
|
|
77
|
+
sock1, sock2 = async_solipsism.socketpair()
|
|
78
|
+
data = array.array("I", [1, 2, 3])
|
|
79
|
+
sock1.send(b"\xDE\xAD\xBE\xEF")
|
|
80
|
+
nbytes = sock2.recv_into(data)
|
|
81
|
+
assert nbytes == 4
|
|
82
|
+
assert data[0] == struct.unpack("I", b"\xDE\xAD\xBE\xEF")[0]
|
|
83
|
+
assert data[1] == 2
|
|
84
|
+
assert data[2] == 3
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_recv_into_array_partial():
|
|
88
|
+
sock1, sock2 = async_solipsism.socketpair()
|
|
89
|
+
data = array.array("I", [1, 2, 3])
|
|
90
|
+
sock1.send(b"\xDE\xAD\xBE\xEF\xCA\xFE")
|
|
91
|
+
nbytes = sock2.recv_into(data, 4)
|
|
92
|
+
assert nbytes == 4
|
|
93
|
+
assert data[0] == struct.unpack("I", b"\xDE\xAD\xBE\xEF")[0]
|
|
94
|
+
assert data[1] == 2
|
|
95
|
+
assert data[2] == 3
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_write_array():
|
|
99
|
+
sock1, sock2 = async_solipsism.socketpair(capacity=100)
|
|
100
|
+
data = array.array("I", range(64))
|
|
101
|
+
nbytes = sock1.send(data)
|
|
102
|
+
assert nbytes == 100
|
|
103
|
+
received = sock2.recv(-1)
|
|
104
|
+
assert len(received) == 100
|
|
105
|
+
assert memoryview(received) == memoryview(data).cast("B")[:100]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize("nbytes", [-1, 5])
|
|
109
|
+
def test_recv_into_bad_nbytes(sock, nbytes):
|
|
110
|
+
with pytest.raises(ValueError):
|
|
111
|
+
sock.recv_into(bytearray(4), nbytes=nbytes)
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: Run tests
|
|
2
|
-
|
|
3
|
-
on: [push]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
strategy:
|
|
9
|
-
matrix:
|
|
10
|
-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v2
|
|
14
|
-
- name: Set up Python ${{ matrix.python-version }}
|
|
15
|
-
uses: actions/setup-python@v2
|
|
16
|
-
with:
|
|
17
|
-
python-version: ${{ matrix.python-version }}
|
|
18
|
-
- name: Install dependencies
|
|
19
|
-
run: pip install -r requirements.txt
|
|
20
|
-
- name: Run flake8
|
|
21
|
-
run: flake8
|
|
22
|
-
- name: Install package
|
|
23
|
-
run: pip install .
|
|
24
|
-
- name: Run pytest
|
|
25
|
-
run: pytest
|
async-solipsism-0.5/setup.cfg
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
name = async-solipsism
|
|
3
|
-
version = attr: src.async_solipsism.__version__
|
|
4
|
-
description = Asyncio event loop that doesn't interact with the outside world
|
|
5
|
-
long_description = file:README.md
|
|
6
|
-
long_description_content_type = text/markdown
|
|
7
|
-
author = Bruce Merry
|
|
8
|
-
author_email = bmerry@gmail.com
|
|
9
|
-
url = https://github.com/bmerry/async-solipsism
|
|
10
|
-
classifiers =
|
|
11
|
-
Development Status :: 3 - Alpha
|
|
12
|
-
Framework :: AsyncIO
|
|
13
|
-
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
-
Topic :: Software Development :: Testing :: Mocking
|
|
15
|
-
|
|
16
|
-
[options]
|
|
17
|
-
package_dir =
|
|
18
|
-
= src
|
|
19
|
-
packages = find:
|
|
20
|
-
python_requires = >= 3.6
|
|
21
|
-
|
|
22
|
-
[options.packages.find]
|
|
23
|
-
where = src
|
|
24
|
-
|
|
25
|
-
[options.extras_require]
|
|
26
|
-
test =
|
|
27
|
-
pytest
|
|
28
|
-
pytest-asyncio
|
|
29
|
-
pytest-mock
|
|
30
|
-
|
|
31
|
-
[egg_info]
|
|
32
|
-
tag_build =
|
|
33
|
-
tag_date = 0
|
|
34
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async-solipsism-0.5 → async_solipsism-0.7}/src/async_solipsism.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|