asyncpg 0.30.0__tar.gz → 0.32.0__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.
Files changed (159) hide show
  1. {asyncpg-0.30.0 → asyncpg-0.32.0}/PKG-INFO +10 -21
  2. {asyncpg-0.30.0 → asyncpg-0.32.0}/README.rst +2 -2
  3. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/_asyncio_compat.py +1 -1
  4. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/_testbase/__init__.py +13 -4
  5. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/_testbase/fuzzer.py +80 -23
  6. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/_version.py +1 -1
  7. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/connect_utils.py +321 -87
  8. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/connection.py +143 -64
  9. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/cursor.py +1 -1
  10. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/introspection.py +8 -10
  11. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/buffer.pxd +8 -1
  12. asyncpg-0.32.0/asyncpg/pgproto/buffer.pxi +3 -0
  13. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/buffer.pyx +18 -6
  14. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/__init__.pxd +2 -0
  15. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/datetime.pyx +4 -4
  16. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/consts.pxi +0 -3
  17. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/pgproto.c +26461 -20849
  18. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/pgproto.pyi +7 -0
  19. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/types.py +43 -31
  20. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/uuid.pyx +9 -3
  21. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pool.py +235 -57
  22. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/prepared_stmt.py +3 -2
  23. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/__init__.py +2 -1
  24. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/array.pyx +2 -2
  25. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/base.pxd +12 -0
  26. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/base.pyx +40 -13
  27. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/pgproto.pyx +2 -1
  28. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/coreproto.pxd +1 -0
  29. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/coreproto.pyx +12 -6
  30. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/encodings.pyx +1 -1
  31. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/pgtypes.pxi +6 -2
  32. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/prepared_stmt.pyx +4 -4
  33. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/protocol.c +54677 -50287
  34. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/protocol.pxd +1 -0
  35. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/protocol.pyi +6 -21
  36. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/protocol.pyx +138 -127
  37. asyncpg-0.32.0/asyncpg/protocol/record/pythoncapi_compat.h +2559 -0
  38. asyncpg-0.32.0/asyncpg/protocol/record/pythoncapi_compat_extras.h +72 -0
  39. asyncpg-0.32.0/asyncpg/protocol/record/recordobj.c +1290 -0
  40. asyncpg-0.32.0/asyncpg/protocol/record/recordobj.h +33 -0
  41. asyncpg-0.32.0/asyncpg/protocol/record.pyi +29 -0
  42. asyncpg-0.30.0/asyncpg/protocol/record/__init__.pxd → asyncpg-0.32.0/asyncpg/protocol/recordcapi.pxd +1 -6
  43. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/scram.pxd +1 -1
  44. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/scram.pyx +6 -16
  45. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/types.py +2 -2
  46. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg.egg-info/PKG-INFO +10 -21
  47. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg.egg-info/SOURCES.txt +7 -2
  48. asyncpg-0.32.0/asyncpg.egg-info/requires.txt +11 -0
  49. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/api/index.rst +14 -1
  50. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/index.rst +2 -2
  51. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/usage.rst +4 -0
  52. {asyncpg-0.30.0 → asyncpg-0.32.0}/pyproject.toml +17 -10
  53. {asyncpg-0.30.0 → asyncpg-0.32.0}/setup.py +35 -11
  54. asyncpg-0.32.0/tests/certs/client.cert.pem +29 -0
  55. asyncpg-0.32.0/tests/certs/client_ca.cert.pem +25 -0
  56. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/gen.py +128 -21
  57. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_adversity.py +26 -3
  58. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_cache_invalidation.py +3 -3
  59. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_cancellation.py +3 -1
  60. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_codecs.py +47 -1
  61. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_connect.py +959 -211
  62. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_copy.py +105 -0
  63. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_cursor.py +30 -0
  64. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_execute.py +26 -0
  65. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_logging.py +12 -0
  66. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_pool.py +647 -71
  67. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_record.py +15 -2
  68. asyncpg-0.32.0/tests/test_scram.py +101 -0
  69. asyncpg-0.32.0/tests/test_subinterpreters.py +66 -0
  70. asyncpg-0.32.0/tests/test_timeout.py +378 -0
  71. asyncpg-0.30.0/AUTHORS +0 -6
  72. asyncpg-0.30.0/asyncpg/protocol/record/recordobj.c +0 -1025
  73. asyncpg-0.30.0/asyncpg/protocol/record/recordobj.h +0 -53
  74. asyncpg-0.30.0/asyncpg.egg-info/requires.txt +0 -31
  75. asyncpg-0.30.0/tests/certs/client.cert.pem +0 -24
  76. asyncpg-0.30.0/tests/certs/client_ca.cert.pem +0 -25
  77. asyncpg-0.30.0/tests/test_timeout.py +0 -154
  78. {asyncpg-0.30.0 → asyncpg-0.32.0}/.flake8 +0 -0
  79. {asyncpg-0.30.0 → asyncpg-0.32.0}/LICENSE +0 -0
  80. {asyncpg-0.30.0 → asyncpg-0.32.0}/MANIFEST.in +0 -0
  81. {asyncpg-0.30.0 → asyncpg-0.32.0}/Makefile +0 -0
  82. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/__init__.py +0 -0
  83. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/cluster.py +0 -0
  84. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/compat.py +0 -0
  85. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/connresource.py +0 -0
  86. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/exceptions/__init__.py +0 -0
  87. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/exceptions/_base.py +0 -0
  88. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/__init__.pxd +0 -0
  89. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/__init__.py +0 -0
  90. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/bits.pyx +0 -0
  91. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/bytea.pyx +0 -0
  92. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/context.pyx +0 -0
  93. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/float.pyx +0 -0
  94. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/geometry.pyx +0 -0
  95. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/hstore.pyx +0 -0
  96. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/int.pyx +0 -0
  97. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/json.pyx +0 -0
  98. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/jsonpath.pyx +0 -0
  99. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/misc.pyx +0 -0
  100. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/network.pyx +0 -0
  101. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/numeric.pyx +0 -0
  102. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/pg_snapshot.pyx +0 -0
  103. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/text.pyx +0 -0
  104. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/tid.pyx +0 -0
  105. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/codecs/uuid.pyx +0 -0
  106. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/cpythonx.pxd +0 -0
  107. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/debug.h +0 -0
  108. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/debug.pxd +0 -0
  109. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/frb.pxd +0 -0
  110. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/frb.pyx +0 -0
  111. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/hton.h +0 -0
  112. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/hton.pxd +0 -0
  113. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/pgproto.pxd +0 -0
  114. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/pgproto.pyx +0 -0
  115. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/tohex.h +0 -0
  116. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/pgproto/tohex.pxd +0 -0
  117. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/__init__.py +0 -0
  118. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/range.pyx +0 -0
  119. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/record.pyx +0 -0
  120. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/codecs/textutils.pyx +0 -0
  121. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/consts.pxi +0 -0
  122. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/cpythonx.pxd +0 -0
  123. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/prepared_stmt.pxd +0 -0
  124. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/settings.pxd +0 -0
  125. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/protocol/settings.pyx +0 -0
  126. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/serverversion.py +0 -0
  127. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/transaction.py +0 -0
  128. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg/utils.py +0 -0
  129. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg.egg-info/dependency_links.txt +0 -0
  130. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg.egg-info/not-zip-safe +0 -0
  131. {asyncpg-0.30.0 → asyncpg-0.32.0}/asyncpg.egg-info/top_level.txt +0 -0
  132. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/Makefile +0 -0
  133. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/_static/theme_overrides.css +0 -0
  134. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/conf.py +0 -0
  135. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/faq.rst +0 -0
  136. {asyncpg-0.30.0 → asyncpg-0.32.0}/docs/installation.rst +0 -0
  137. {asyncpg-0.30.0 → asyncpg-0.32.0}/performance.png +0 -0
  138. {asyncpg-0.30.0 → asyncpg-0.32.0}/setup.cfg +0 -0
  139. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/__init__.py +0 -0
  140. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/ca.cert.pem +0 -0
  141. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/ca.crl.pem +0 -0
  142. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/ca.key.pem +0 -0
  143. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/client.csr.pem +0 -0
  144. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/client.key.pem +0 -0
  145. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/client.key.protected.pem +0 -0
  146. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/client_ca.key.pem +0 -0
  147. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/server.cert.pem +0 -0
  148. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/server.crl.pem +0 -0
  149. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/certs/server.key.pem +0 -0
  150. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test__environment.py +0 -0
  151. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test__sourcecode.py +0 -0
  152. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_exceptions.py +0 -0
  153. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_introspection.py +0 -0
  154. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_listeners.py +0 -0
  155. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_prepare.py +0 -0
  156. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_test.py +0 -0
  157. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_transaction.py +0 -0
  158. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_types.py +0 -0
  159. {asyncpg-0.30.0 → asyncpg-0.32.0}/tests/test_utils.py +0 -0
@@ -1,46 +1,35 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: asyncpg
3
- Version: 0.30.0
3
+ Version: 0.32.0
4
4
  Summary: An asyncio PostgreSQL driver
5
5
  Author-email: MagicStack Inc <hello@magic.io>
6
- License: Apache License, Version 2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: github, https://github.com/MagicStack/asyncpg
8
8
  Keywords: database,postgres
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Framework :: AsyncIO
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: Apache Software License
13
12
  Classifier: Operating System :: POSIX
14
13
  Classifier: Operating System :: MacOS :: MacOS X
15
14
  Classifier: Operating System :: Microsoft :: Windows
16
15
  Classifier: Programming Language :: Python :: 3 :: Only
17
- Classifier: Programming Language :: Python :: 3.8
18
16
  Classifier: Programming Language :: Python :: 3.9
19
17
  Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
23
24
  Classifier: Topic :: Database :: Front-Ends
24
- Requires-Python: >=3.8.0
25
+ Requires-Python: >=3.9.0
25
26
  Description-Content-Type: text/x-rst
26
27
  License-File: LICENSE
27
- License-File: AUTHORS
28
28
  Requires-Dist: async_timeout>=4.0.3; python_version < "3.11.0"
29
29
  Provides-Extra: gssauth
30
30
  Requires-Dist: gssapi; platform_system != "Windows" and extra == "gssauth"
31
31
  Requires-Dist: sspilib; platform_system == "Windows" and extra == "gssauth"
32
- Provides-Extra: test
33
- Requires-Dist: flake8~=6.1; extra == "test"
34
- Requires-Dist: flake8-pyi~=24.1.0; extra == "test"
35
- Requires-Dist: distro~=1.9.0; extra == "test"
36
- Requires-Dist: uvloop>=0.15.3; (platform_system != "Windows" and python_version < "3.14.0") and extra == "test"
37
- Requires-Dist: gssapi; platform_system == "Linux" and extra == "test"
38
- Requires-Dist: k5test; platform_system == "Linux" and extra == "test"
39
- Requires-Dist: sspilib; platform_system == "Windows" and extra == "test"
40
- Requires-Dist: mypy~=1.8.0; extra == "test"
41
- Provides-Extra: docs
42
- Requires-Dist: Sphinx~=8.1.3; extra == "docs"
43
- Requires-Dist: sphinx_rtd_theme>=1.2.2; extra == "docs"
32
+ Dynamic: license-file
44
33
 
45
34
  asyncpg -- A fast PostgreSQL Database Client Library for Python/asyncio
46
35
  =======================================================================
@@ -57,8 +46,8 @@ of PostgreSQL server binary protocol for use with Python's ``asyncio``
57
46
  framework. You can read more about asyncpg in an introductory
58
47
  `blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
59
48
 
60
- asyncpg requires Python 3.8 or later and is supported for PostgreSQL
61
- versions 9.5 to 17. Other PostgreSQL versions or other databases
49
+ asyncpg requires Python 3.9 or later and is supported for PostgreSQL
50
+ versions 9.5 to 19. Other PostgreSQL versions or other databases
62
51
  implementing the PostgreSQL protocol *may* work, but are not being
63
52
  actively tested.
64
53
 
@@ -13,8 +13,8 @@ of PostgreSQL server binary protocol for use with Python's ``asyncio``
13
13
  framework. You can read more about asyncpg in an introductory
14
14
  `blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
15
15
 
16
- asyncpg requires Python 3.8 or later and is supported for PostgreSQL
17
- versions 9.5 to 17. Other PostgreSQL versions or other databases
16
+ asyncpg requires Python 3.9 or later and is supported for PostgreSQL
17
+ versions 9.5 to 19. Other PostgreSQL versions or other databases
18
18
  implementing the PostgreSQL protocol *may* work, but are not being
19
19
  actively tested.
20
20
 
@@ -33,7 +33,7 @@ async def wait_for(fut: compat.Awaitable[_T], timeout: float | None) -> _T:
33
33
 
34
34
  If the wait is cancelled, the task is also cancelled.
35
35
 
36
- If the task supresses the cancellation and returns a value instead,
36
+ If the task suppresses the cancellation and returns a value instead,
37
37
  that value is returned.
38
38
 
39
39
  This function is a coroutine.
@@ -101,9 +101,10 @@ class TestCase(unittest.TestCase, metaclass=TestCaseMeta):
101
101
  def setUpClass(cls):
102
102
  if os.environ.get('USE_UVLOOP'):
103
103
  import uvloop
104
- asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
104
+ loop = uvloop.new_event_loop()
105
+ else:
106
+ loop = asyncio.new_event_loop()
105
107
 
106
- loop = asyncio.new_event_loop()
107
108
  asyncio.set_event_loop(None)
108
109
  cls.loop = loop
109
110
 
@@ -267,6 +268,7 @@ def _shutdown_cluster(cluster):
267
268
 
268
269
 
269
270
  def create_pool(dsn=None, *,
271
+ init_size=None,
270
272
  min_size=10,
271
273
  max_size=10,
272
274
  max_queries=50000,
@@ -281,6 +283,7 @@ def create_pool(dsn=None, *,
281
283
  **connect_kwargs):
282
284
  return pool_class(
283
285
  dsn,
286
+ init_size=init_size,
284
287
  min_size=min_size,
285
288
  max_size=max_size,
286
289
  max_queries=max_queries,
@@ -366,10 +369,16 @@ class ClusterTestCase(TestCase):
366
369
  self._pools = []
367
370
 
368
371
  def tearDown(self):
369
- super().tearDown()
372
+ maintenance_tasks = []
370
373
  for pool in self._pools:
371
374
  pool.terminate()
375
+ if pool._maintenance_task is not None:
376
+ maintenance_tasks.append(pool._maintenance_task)
377
+ if maintenance_tasks:
378
+ self.loop.run_until_complete(asyncio.gather(
379
+ *maintenance_tasks, return_exceptions=True))
372
380
  self._pools = []
381
+ super().tearDown()
373
382
 
374
383
  def create_pool(self, pool_class=pg_pool.Pool,
375
384
  connection_class=pg_connection.Connection, **kwargs):
@@ -402,7 +411,7 @@ class ProxiedClusterTestCase(ClusterTestCase):
402
411
  host = '127.0.0.1'
403
412
  cls.proxy = fuzzer.TCPFuzzingProxy(
404
413
  backend_host=host,
405
- backend_port=conn_spec['port'],
414
+ backend_port=int(conn_spec['port']),
406
415
  )
407
416
  cls.proxy.start()
408
417
 
@@ -6,6 +6,7 @@
6
6
 
7
7
 
8
8
  import asyncio
9
+ import contextlib
9
10
  import socket
10
11
  import threading
11
12
  import typing
@@ -34,6 +35,7 @@ class TCPFuzzingProxy:
34
35
  self.connections = {}
35
36
  self.sock = None
36
37
  self.listen_task = None
38
+ self.connection_factory = Connection
37
39
 
38
40
  async def _wait(self, work):
39
41
  work_task = asyncio.ensure_future(work)
@@ -98,11 +100,13 @@ class TCPFuzzingProxy:
98
100
  try:
99
101
  await self.listen_task
100
102
  finally:
103
+ tasks = list(self.connections.values())
101
104
  for c in list(self.connections):
102
105
  c.close()
103
- await asyncio.sleep(0.01)
106
+ await asyncio.gather(*tasks, return_exceptions=True)
104
107
  if hasattr(self.loop, 'remove_reader'):
105
- self.loop.remove_reader(self.sock.fileno())
108
+ with contextlib.suppress(NotImplementedError):
109
+ self.loop.remove_reader(self.sock.fileno())
106
110
  self.sock.close()
107
111
 
108
112
  async def listen(self):
@@ -119,7 +123,7 @@ class TCPFuzzingProxy:
119
123
  except StopServer:
120
124
  break
121
125
 
122
- conn = Connection(client_sock, backend_sock, self)
126
+ conn = self.connection_factory(client_sock, backend_sock, self)
123
127
  conn_task = self.loop.create_task(conn.handle())
124
128
  self.connections[conn] = conn_task
125
129
 
@@ -141,13 +145,13 @@ class TCPFuzzingProxy:
141
145
  self.restore_connectivity()
142
146
 
143
147
  def _close_connection(self, connection):
144
- conn_task = self.connections.pop(connection, None)
148
+ conn_task = self.connections.get(connection)
145
149
  if conn_task is not None:
146
150
  conn_task.cancel()
147
151
 
148
152
  def close_all_connections(self):
149
153
  for conn in list(self.connections):
150
- self.loop.call_soon_threadsafe(self._close_connection, conn)
154
+ self.loop.call_soon_threadsafe(conn.close)
151
155
 
152
156
 
153
157
  class Connection:
@@ -161,6 +165,27 @@ class Connection:
161
165
  self.proxy_to_backend_task = None
162
166
  self.proxy_from_backend_task = None
163
167
  self.is_closed = False
168
+ self.client_reader = None
169
+ self.client_writer = None
170
+
171
+ async def prepare(self):
172
+ """Optionally negotiate or inject faults before forwarding bytes."""
173
+ return True
174
+
175
+ async def use_client_stream(self, ssl_context=None):
176
+ """Wrap the accepted socket in a stream, optionally with direct TLS."""
177
+ self.client_reader = asyncio.StreamReader()
178
+ protocol = asyncio.StreamReaderProtocol(self.client_reader)
179
+ transport, _ = await self.loop.connect_accepted_socket(
180
+ lambda: protocol, self.client_sock, ssl=ssl_context)
181
+ self.client_writer = asyncio.StreamWriter(
182
+ transport, protocol, self.client_reader, self.loop)
183
+
184
+ async def start_tls(self, ssl_context):
185
+ writer = self.client_writer
186
+ writer._transport = await self.loop.start_tls(
187
+ writer.transport, writer._protocol, ssl_context, server_side=True)
188
+ writer._protocol._over_ssl = True
164
189
 
165
190
  def close(self):
166
191
  if self.is_closed:
@@ -170,48 +195,72 @@ class Connection:
170
195
 
171
196
  if self.proxy_to_backend_task is not None:
172
197
  self.proxy_to_backend_task.cancel()
173
- self.proxy_to_backend_task = None
174
198
 
175
199
  if self.proxy_from_backend_task is not None:
176
200
  self.proxy_from_backend_task.cancel()
177
- self.proxy_from_backend_task = None
178
201
 
179
202
  self.proxy._close_connection(self)
180
203
 
181
204
  async def handle(self):
182
- self.proxy_to_backend_task = asyncio.ensure_future(
183
- self.proxy_to_backend())
205
+ try:
206
+ if not await self.prepare():
207
+ return
184
208
 
185
- self.proxy_from_backend_task = asyncio.ensure_future(
186
- self.proxy_from_backend())
209
+ self.proxy_to_backend_task = asyncio.ensure_future(
210
+ self.proxy_to_backend())
211
+ self.proxy_from_backend_task = asyncio.ensure_future(
212
+ self.proxy_from_backend())
187
213
 
188
- try:
189
214
  await asyncio.wait(
190
215
  [self.proxy_to_backend_task, self.proxy_from_backend_task],
191
216
  return_when=asyncio.FIRST_COMPLETED)
192
217
 
218
+ except ConnectionError:
219
+ pass
220
+
193
221
  finally:
222
+ # Relay completion can schedule close() while cleanup is awaiting
223
+ # its children. Do not let that callback cancel cleanup itself.
224
+ self.is_closed = True
194
225
  if self.proxy_to_backend_task is not None:
195
226
  self.proxy_to_backend_task.cancel()
196
227
 
197
228
  if self.proxy_from_backend_task is not None:
198
229
  self.proxy_from_backend_task.cancel()
199
230
 
231
+ await asyncio.gather(*(
232
+ task for task in (self.proxy_to_backend_task,
233
+ self.proxy_from_backend_task)
234
+ if task is not None
235
+ ), return_exceptions=True)
236
+
200
237
  # Asyncio fails to properly remove the readers and writers
201
238
  # when the task doing recv() or send() is cancelled, so
202
239
  # we must remove the readers and writers manually before
203
240
  # closing the sockets.
204
- self.loop.remove_reader(self.client_sock.fileno())
205
- self.loop.remove_writer(self.client_sock.fileno())
206
- self.loop.remove_reader(self.backend_sock.fileno())
207
- self.loop.remove_writer(self.backend_sock.fileno())
208
-
209
- self.client_sock.close()
210
- self.backend_sock.close()
241
+ sockets = [self.backend_sock]
242
+ if self.client_writer is None:
243
+ sockets.append(self.client_sock)
244
+ else:
245
+ self.client_writer.close()
246
+ with contextlib.suppress(ConnectionError):
247
+ await self.client_writer.wait_closed()
248
+ for sock in sockets:
249
+ if sock.fileno() < 0:
250
+ continue
251
+ # ProactorEventLoop has no reader/writer registration API.
252
+ with contextlib.suppress(NotImplementedError):
253
+ self.loop.remove_reader(sock.fileno())
254
+ self.loop.remove_writer(sock.fileno())
255
+ sock.close()
256
+ self.proxy.connections.pop(self, None)
211
257
 
212
258
  async def _read(self, sock, n):
213
- read_task = asyncio.ensure_future(
214
- self.loop.sock_recv(sock, n))
259
+ if sock is self.client_sock and self.client_reader is not None:
260
+ read = self.client_reader.read(n)
261
+ else:
262
+ read = self.loop.sock_recv(sock, n)
263
+ read_task = asyncio.ensure_future(read)
215
264
  conn_event_task = asyncio.ensure_future(
216
265
  self.connectivity_loss.wait())
217
266
 
@@ -230,10 +279,16 @@ class Connection:
230
279
  read_task.cancel()
231
280
  if not conn_event_task.done():
232
281
  conn_event_task.cancel()
282
+ await asyncio.gather(read_task, conn_event_task,
283
+ return_exceptions=True)
233
284
 
234
285
  async def _write(self, sock, data):
235
- write_task = asyncio.ensure_future(
236
- self.loop.sock_sendall(sock, data))
286
+ if sock is self.client_sock and self.client_writer is not None:
287
+ self.client_writer.write(data)
288
+ write = self.client_writer.drain()
289
+ else:
290
+ write = self.loop.sock_sendall(sock, data)
291
+ write_task = asyncio.ensure_future(write)
237
292
  conn_event_task = asyncio.ensure_future(
238
293
  self.connectivity_loss.wait())
239
294
 
@@ -252,6 +307,8 @@ class Connection:
252
307
  write_task.cancel()
253
308
  if not conn_event_task.done():
254
309
  conn_event_task.cancel()
310
+ await asyncio.gather(write_task, conn_event_task,
311
+ return_exceptions=True)
255
312
 
256
313
  async def proxy_to_backend(self):
257
314
  buf = None
@@ -14,4 +14,4 @@ from __future__ import annotations
14
14
 
15
15
  import typing
16
16
 
17
- __version__: typing.Final = '0.30.0'
17
+ __version__: typing.Final = '0.32.0'