polyapi-python 0.3.16.dev1__tar.gz → 0.3.17__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.
- {polyapi_python-0.3.16.dev1/polyapi_python.egg-info → polyapi_python-0.3.17}/PKG-INFO +1 -1
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/http_client.py +16 -6
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17/polyapi_python.egg-info}/PKG-INFO +1 -1
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/pyproject.toml +1 -1
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_async_proof.py +86 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/LICENSE +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/README.md +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/api.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/cli_constants.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/client.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/config.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/deployables.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/generate.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/poly_schemas.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/poly_tables.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/schema.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/server.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/sync.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/utils.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/variables.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi/webhook.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi_python.egg-info/SOURCES.txt +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/setup.cfg +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_api.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_generate.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_poly_custom.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_schema.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_server.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_tabi.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/tests/test_variables.py +0 -0
|
@@ -3,6 +3,7 @@ import httpx
|
|
|
3
3
|
|
|
4
4
|
_sync_client: httpx.Client | None = None
|
|
5
5
|
_async_client: httpx.AsyncClient | None = None
|
|
6
|
+
_async_client_loop: asyncio.AbstractEventLoop | None = None
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def _get_sync_client() -> httpx.Client:
|
|
@@ -13,9 +14,11 @@ def _get_sync_client() -> httpx.Client:
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def _get_async_client() -> httpx.AsyncClient:
|
|
16
|
-
global _async_client
|
|
17
|
-
|
|
17
|
+
global _async_client, _async_client_loop
|
|
18
|
+
current_loop = asyncio.get_running_loop()
|
|
19
|
+
if _async_client is None or _async_client_loop is not current_loop:
|
|
18
20
|
_async_client = httpx.AsyncClient(timeout=None)
|
|
21
|
+
_async_client_loop = current_loop
|
|
19
22
|
return _async_client
|
|
20
23
|
|
|
21
24
|
|
|
@@ -66,8 +69,15 @@ def close():
|
|
|
66
69
|
_sync_client = None
|
|
67
70
|
|
|
68
71
|
async def close_async():
|
|
69
|
-
global _sync_client, _async_client
|
|
72
|
+
global _sync_client, _async_client, _async_client_loop
|
|
70
73
|
close()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
client = _async_client
|
|
75
|
+
client_loop = _async_client_loop
|
|
76
|
+
_async_client = None
|
|
77
|
+
_async_client_loop = None
|
|
78
|
+
if client is None:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
current_loop = asyncio.get_running_loop()
|
|
82
|
+
if client_loop is current_loop:
|
|
83
|
+
await client.aclose()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "polyapi-python"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.17"
|
|
8
8
|
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
|
|
9
9
|
authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
|
|
10
10
|
dependencies = [
|
|
@@ -56,10 +56,12 @@ class TestHttpClientPairing:
|
|
|
56
56
|
# Reset singletons so each test starts fresh
|
|
57
57
|
http_client._sync_client = None
|
|
58
58
|
http_client._async_client = None
|
|
59
|
+
http_client._async_client_loop = None
|
|
59
60
|
|
|
60
61
|
def teardown_method(self):
|
|
61
62
|
http_client._sync_client = None
|
|
62
63
|
http_client._async_client = None
|
|
64
|
+
http_client._async_client_loop = None
|
|
63
65
|
|
|
64
66
|
@patch.object(httpx.Client, "post", return_value=_fake_response())
|
|
65
67
|
def test_sync_post_uses_sync_client(self, mock_post):
|
|
@@ -79,6 +81,90 @@ class TestHttpClientPairing:
|
|
|
79
81
|
mock_post.assert_called_once()
|
|
80
82
|
assert resp.status_code == 200
|
|
81
83
|
assert http_client._async_client is not None
|
|
84
|
+
assert http_client._async_client_loop is not None
|
|
85
|
+
|
|
86
|
+
def test_async_post_reuses_client_within_same_loop(self):
|
|
87
|
+
first_client = MagicMock()
|
|
88
|
+
first_client.post = AsyncMock(return_value=_fake_response())
|
|
89
|
+
|
|
90
|
+
with patch("polyapi.http_client.httpx.AsyncClient", return_value=first_client) as mock_async_client:
|
|
91
|
+
async def _run():
|
|
92
|
+
first_response = await http_client.async_post("https://example.com/first", json={})
|
|
93
|
+
second_response = await http_client.async_post("https://example.com/second", json={})
|
|
94
|
+
return first_response, second_response, asyncio.get_running_loop()
|
|
95
|
+
|
|
96
|
+
first_response, second_response, current_loop = asyncio.run(_run())
|
|
97
|
+
|
|
98
|
+
assert first_response.status_code == 200
|
|
99
|
+
assert second_response.status_code == 200
|
|
100
|
+
assert mock_async_client.call_count == 1
|
|
101
|
+
assert first_client.post.await_count == 2
|
|
102
|
+
assert http_client._async_client is first_client
|
|
103
|
+
assert http_client._async_client_loop is current_loop
|
|
104
|
+
|
|
105
|
+
def test_async_post_recreates_client_after_loop_change(self):
|
|
106
|
+
first_client = MagicMock()
|
|
107
|
+
first_client.post = AsyncMock(return_value=_fake_response())
|
|
108
|
+
second_client = MagicMock()
|
|
109
|
+
second_client.post = AsyncMock(return_value=_fake_response())
|
|
110
|
+
|
|
111
|
+
with patch(
|
|
112
|
+
"polyapi.http_client.httpx.AsyncClient",
|
|
113
|
+
side_effect=[first_client, second_client],
|
|
114
|
+
) as mock_async_client:
|
|
115
|
+
async def _run_once(url: str):
|
|
116
|
+
response = await http_client.async_post(url, json={})
|
|
117
|
+
return response, http_client._async_client, asyncio.get_running_loop()
|
|
118
|
+
|
|
119
|
+
first_response, first_cached_client, first_loop = asyncio.run(_run_once("https://example.com/first"))
|
|
120
|
+
second_response, second_cached_client, second_loop = asyncio.run(_run_once("https://example.com/second"))
|
|
121
|
+
|
|
122
|
+
assert first_response.status_code == 200
|
|
123
|
+
assert second_response.status_code == 200
|
|
124
|
+
assert mock_async_client.call_count == 2
|
|
125
|
+
assert first_client.post.await_count == 1
|
|
126
|
+
assert second_client.post.await_count == 1
|
|
127
|
+
assert first_cached_client is first_client
|
|
128
|
+
assert second_cached_client is second_client
|
|
129
|
+
assert first_loop is not second_loop
|
|
130
|
+
assert http_client._async_client is second_client
|
|
131
|
+
assert http_client._async_client_loop is second_loop
|
|
132
|
+
|
|
133
|
+
def test_close_async_clears_cached_client_for_current_loop(self):
|
|
134
|
+
async def _run():
|
|
135
|
+
cached_client = MagicMock()
|
|
136
|
+
cached_client.aclose = AsyncMock()
|
|
137
|
+
http_client._async_client = cached_client
|
|
138
|
+
http_client._async_client_loop = asyncio.get_running_loop()
|
|
139
|
+
|
|
140
|
+
await http_client.close_async()
|
|
141
|
+
|
|
142
|
+
return cached_client
|
|
143
|
+
|
|
144
|
+
cached_client = asyncio.run(_run())
|
|
145
|
+
|
|
146
|
+
cached_client.aclose.assert_awaited_once()
|
|
147
|
+
assert http_client._async_client is None
|
|
148
|
+
assert http_client._async_client_loop is None
|
|
149
|
+
|
|
150
|
+
def test_close_async_drops_stale_client_without_cross_loop_close(self):
|
|
151
|
+
stale_client = MagicMock()
|
|
152
|
+
stale_client.aclose = AsyncMock()
|
|
153
|
+
|
|
154
|
+
async def _seed_stale_client():
|
|
155
|
+
http_client._async_client = stale_client
|
|
156
|
+
http_client._async_client_loop = asyncio.get_running_loop()
|
|
157
|
+
|
|
158
|
+
asyncio.run(_seed_stale_client())
|
|
159
|
+
|
|
160
|
+
async def _close_on_new_loop():
|
|
161
|
+
await http_client.close_async()
|
|
162
|
+
|
|
163
|
+
asyncio.run(_close_on_new_loop())
|
|
164
|
+
|
|
165
|
+
stale_client.aclose.assert_not_awaited()
|
|
166
|
+
assert http_client._async_client is None
|
|
167
|
+
assert http_client._async_client_loop is None
|
|
82
168
|
|
|
83
169
|
@patch.object(httpx.Client, "get", return_value=_fake_response())
|
|
84
170
|
def test_sync_get(self, mock_get):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polyapi_python-0.3.16.dev1 → polyapi_python-0.3.17}/polyapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|