Skip to content

Commit cf86d1b

Browse files
committed
Merge remote-tracking branch 'origin/master' into client-encoding-test
2 parents a8b3188 + 89d5bd0 commit cf86d1b

39 files changed

+953
-345
lines changed

.coveragerc

-12
This file was deleted.

.flake8

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[flake8]
22
ignore = E402,E731,W503,W504,E252
3-
exclude = .git,__pycache__,build,dist,.eggs,.github,.local,.venv
3+
exclude = .git,__pycache__,build,dist,.eggs,.github,.local,.venv,.tox

.github/workflows/release.yml

+6-8
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ jobs:
8080
- uses: actions/setup-python@v4
8181
with:
8282
python-version: "3.x"
83-
- run: pip install cibuildwheel==2.10.2
83+
- run: pip install cibuildwheel==2.13.1
8484
- id: set-matrix
8585
run: |
8686
MATRIX_INCLUDE=$(
8787
{
88-
cibuildwheel --print-build-identifiers --platform linux --arch x86_64,aarch64 | grep cp | jq -Rc '{"only": inputs, "os": "ubuntu-latest"}' \
89-
&& cibuildwheel --print-build-identifiers --platform macos --arch x86_64,arm64 | grep cp | jq -Rc '{"only": inputs, "os": "macos-latest"}' \
90-
&& cibuildwheel --print-build-identifiers --platform windows --arch x86,AMD64 | grep cp | jq -Rc '{"only": inputs, "os": "windows-latest"}'
88+
cibuildwheel --print-build-identifiers --platform linux --arch x86_64,aarch64 | grep cp | jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
89+
&& cibuildwheel --print-build-identifiers --platform macos --arch x86_64,arm64 | grep cp | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
90+
&& cibuildwheel --print-build-identifiers --platform windows --arch x86,AMD64 | grep cp | jq -nRc '{"only": inputs, "os": "windows-latest"}'
9191
} | jq -sc
9292
)
9393
echo "include=$MATRIX_INCLUDE" >> $GITHUB_OUTPUT
@@ -118,13 +118,11 @@ jobs:
118118
if: runner.os == 'Linux'
119119
uses: docker/setup-qemu-action@v2
120120

121-
- uses: pypa/cibuildwheel@v2.10.2
121+
- uses: pypa/cibuildwheel@v2.13.1
122122
with:
123123
only: ${{ matrix.only }}
124124
env:
125125
CIBW_BUILD_VERBOSITY: 1
126-
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
127-
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
128126

129127
- uses: actions/upload-artifact@v3
130128
with:
@@ -152,7 +150,7 @@ jobs:
152150

153151
- name: Build docs
154152
run: |
155-
pip install -e .[dev]
153+
pip install -e .[docs]
156154
make htmldocs
157155
158156
- name: Checkout gh-pages

.github/workflows/tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# job.
1818
strategy:
1919
matrix:
20-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
20+
python-version: ["3.8", "3.9", "3.10", "3.11"]
2121
os: [ubuntu-latest, macos-latest, windows-latest]
2222
loop: [asyncio, uvloop]
2323
exclude:

Makefile

+7-8
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,26 @@ clean:
2020

2121

2222
compile:
23-
$(PYTHON) setup.py build_ext --inplace --cython-always
23+
env ASYNCPG_BUILD_CYTHON_ALWAYS=1 $(PYTHON) -m pip install -e .
2424

2525

2626
debug:
27-
ASYNCPG_DEBUG=1 $(PYTHON) setup.py build_ext --inplace
28-
27+
env ASYNCPG_DEBUG=1 $(PYTHON) -m pip install -e .
2928

3029
test:
31-
PYTHONASYNCIODEBUG=1 $(PYTHON) setup.py test
32-
$(PYTHON) setup.py test
33-
USE_UVLOOP=1 $(PYTHON) setup.py test
30+
PYTHONASYNCIODEBUG=1 $(PYTHON) -m unittest -v tests.suite
31+
$(PYTHON) -m unittest -v tests.suite
32+
USE_UVLOOP=1 $(PYTHON) -m unittest -v tests.suite
3433

3534

3635
testinstalled:
3736
cd "$${HOME}" && $(PYTHON) $(ROOT)/tests/__init__.py
3837

3938

4039
quicktest:
41-
$(PYTHON) setup.py test
40+
$(PYTHON) -m unittest -v tests.suite
4241

4342

4443
htmldocs:
45-
$(PYTHON) setup.py build_ext --inplace
44+
$(PYTHON) -m pip install -e .[docs]
4645
$(MAKE) -C docs html

README.rst

+5-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ of PostgreSQL server binary protocol for use with Python's ``asyncio``
1313
framework. You can read more about asyncpg in an introductory
1414
`blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
1515

16-
asyncpg requires Python 3.7 or later and is supported for PostgreSQL
16+
asyncpg requires Python 3.8 or later and is supported for PostgreSQL
1717
versions 9.5 to 15. Older PostgreSQL versions or other databases implementing
1818
the PostgreSQL protocol *may* work, but are not being actively tested.
1919

@@ -28,15 +28,14 @@ The project documentation can be found
2828
Performance
2929
-----------
3030

31-
In our testing asyncpg is, on average, **3x** faster than psycopg2
32-
(and its asyncio variant -- aiopg).
31+
In our testing asyncpg is, on average, **5x** faster than psycopg3.
3332

34-
.. image:: https://raw.githubusercontent.com/MagicStack/asyncpg/master/performance.png
35-
:target: https://gistpreview.github.io/?b8eac294ac85da177ff82f784ff2cb60
33+
.. image:: https://raw.githubusercontent.com/MagicStack/asyncpg/master/performance.png?fddca40ab0
34+
:target: https://gistpreview.github.io/?0ed296e93523831ea0918d42dd1258c2
3635

3736
The above results are a geometric mean of benchmarks obtained with PostgreSQL
3837
`client driver benchmarking toolbench <https://github.com/MagicStack/pgbench>`_
39-
in November 2020 (click on the chart to see full details).
38+
in June 2023 (click on the chart to see full details).
4039

4140

4241
Features

asyncpg/_testbase/__init__.py

+90
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,93 @@ def tearDown(self):
435435
self.con = None
436436
finally:
437437
super().tearDown()
438+
439+
440+
class HotStandbyTestCase(ClusterTestCase):
441+
442+
@classmethod
443+
def setup_cluster(cls):
444+
cls.master_cluster = cls.new_cluster(pg_cluster.TempCluster)
445+
cls.start_cluster(
446+
cls.master_cluster,
447+
server_settings={
448+
'max_wal_senders': 10,
449+
'wal_level': 'hot_standby'
450+
}
451+
)
452+
453+
con = None
454+
455+
try:
456+
con = cls.loop.run_until_complete(
457+
cls.master_cluster.connect(
458+
database='postgres', user='postgres', loop=cls.loop))
459+
460+
cls.loop.run_until_complete(
461+
con.execute('''
462+
CREATE ROLE replication WITH LOGIN REPLICATION
463+
'''))
464+
465+
cls.master_cluster.trust_local_replication_by('replication')
466+
467+
conn_spec = cls.master_cluster.get_connection_spec()
468+
469+
cls.standby_cluster = cls.new_cluster(
470+
pg_cluster.HotStandbyCluster,
471+
cluster_kwargs={
472+
'master': conn_spec,
473+
'replication_user': 'replication'
474+
}
475+
)
476+
cls.start_cluster(
477+
cls.standby_cluster,
478+
server_settings={
479+
'hot_standby': True
480+
}
481+
)
482+
483+
finally:
484+
if con is not None:
485+
cls.loop.run_until_complete(con.close())
486+
487+
@classmethod
488+
def get_cluster_connection_spec(cls, cluster, kwargs={}):
489+
conn_spec = cluster.get_connection_spec()
490+
if kwargs.get('dsn'):
491+
conn_spec.pop('host')
492+
conn_spec.update(kwargs)
493+
if not os.environ.get('PGHOST') and not kwargs.get('dsn'):
494+
if 'database' not in conn_spec:
495+
conn_spec['database'] = 'postgres'
496+
if 'user' not in conn_spec:
497+
conn_spec['user'] = 'postgres'
498+
return conn_spec
499+
500+
@classmethod
501+
def get_connection_spec(cls, kwargs={}):
502+
primary_spec = cls.get_cluster_connection_spec(
503+
cls.master_cluster, kwargs
504+
)
505+
standby_spec = cls.get_cluster_connection_spec(
506+
cls.standby_cluster, kwargs
507+
)
508+
return {
509+
'host': [primary_spec['host'], standby_spec['host']],
510+
'port': [primary_spec['port'], standby_spec['port']],
511+
'database': primary_spec['database'],
512+
'user': primary_spec['user'],
513+
**kwargs
514+
}
515+
516+
@classmethod
517+
def connect_primary(cls, **kwargs):
518+
conn_spec = cls.get_cluster_connection_spec(cls.master_cluster, kwargs)
519+
return pg_connection.connect(**conn_spec, loop=cls.loop)
520+
521+
@classmethod
522+
def connect_standby(cls, **kwargs):
523+
conn_spec = cls.get_cluster_connection_spec(
524+
cls.standby_cluster,
525+
kwargs
526+
)
527+
return pg_connection.connect(**conn_spec, loop=cls.loop)

asyncpg/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
# supported platforms, publish the packages on PyPI, merge the PR
1111
# to the target branch, create a Git tag pointing to the commit.
1212

13-
__version__ = '0.28.0.dev0'
13+
__version__ = '0.29.0.dev0'

asyncpg/cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ def init(self, **settings):
626626
'pg_basebackup init exited with status {:d}:\n{}'.format(
627627
process.returncode, output.decode()))
628628

629-
if self._pg_version <= (11, 0):
629+
if self._pg_version < (12, 0):
630630
with open(os.path.join(self._data_dir, 'recovery.conf'), 'w') as f:
631631
f.write(textwrap.dedent("""\
632632
standby_mode = 'on'

asyncpg/compat.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import asyncio
99
import pathlib
1010
import platform
11+
import typing
1112

1213

1314
SYSTEM = platform.uname().system
@@ -18,7 +19,7 @@
1819

1920
CSIDL_APPDATA = 0x001a
2021

21-
def get_pg_home_directory() -> pathlib.Path:
22+
def get_pg_home_directory() -> typing.Optional[pathlib.Path]:
2223
# We cannot simply use expanduser() as that returns the user's
2324
# home directory, whereas Postgres stores its config in
2425
# %AppData% on Windows.
@@ -30,8 +31,11 @@ def get_pg_home_directory() -> pathlib.Path:
3031
return pathlib.Path(buf.value) / 'postgresql'
3132

3233
else:
33-
def get_pg_home_directory() -> pathlib.Path:
34-
return pathlib.Path.home()
34+
def get_pg_home_directory() -> typing.Optional[pathlib.Path]:
35+
try:
36+
return pathlib.Path.home()
37+
except (RuntimeError, KeyError):
38+
return None
3539

3640

3741
async def wait_closed(stream):

0 commit comments

Comments
 (0)