Skip to content

Commit f58e44a

Browse files
committed
Add Windows support (#31)
1 parent 93733db commit f58e44a

File tree

9 files changed

+131
-23
lines changed

9 files changed

+131
-23
lines changed

.ci/appveyor.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
services:
2+
- postgresql95
3+
4+
environment:
5+
global:
6+
PGINSTALLATION: C:\\Program Files\\PostgreSQL\\9.5\\bin
7+
TWINE_USERNAME: magicstack-ci
8+
TWINE_PASSWORD:
9+
secure: lJ+XbDBgnR3SA1hyBl7kZ+fOJYQYVlZ2A27Vt9p+TNJ6Fsv41Yx7O0fb9bTdiVV+
10+
11+
matrix:
12+
- PYTHON: "C:\\Python35\\python.exe"
13+
- PYTHON: "C:\\Python35-x64\\python.exe"
14+
15+
install:
16+
- "%PYTHON% -m pip install --upgrade pip wheel setuptools"
17+
- "%PYTHON% -m pip install -r .ci/requirements-win.txt"
18+
19+
build_script:
20+
- "%PYTHON% setup.py build_ext --inplace"
21+
22+
test_script:
23+
- "%PYTHON% -m unittest discover -s tests"
24+
25+
after_test:
26+
- "%PYTHON% setup.py bdist_wheel"
27+
28+
artifacts:
29+
- path: dist\*
30+
31+
deploy_script:
32+
- cmd: "%PYTHON% -m twine upload dist\\*.whl"
33+
on:
34+
branch: master
35+
appveyor_repo_tag: true

.ci/requirements-win.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cython>=0.24
2+
twine

asyncpg/cluster.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323

2424
_system = platform.uname().system
2525

26+
if _system == 'Windows':
27+
def platform_exe(name):
28+
if name.endswith('.exe'):
29+
return name
30+
return name + '.exe'
31+
else:
32+
def platform_exe(name):
33+
return name
34+
35+
2636
if _system == 'Linux':
2737
def ensure_dead_with_parent():
2838
import ctypes
@@ -165,13 +175,29 @@ def start(self, wait=60, *, server_settings={}, **opts):
165175
for k, v in server_settings.items():
166176
extra_args.extend(['-c', '{}={}'.format(k, v)])
167177

168-
self._daemon_process = \
169-
subprocess.Popen(
170-
[self._postgres, '-D', self._data_dir, *extra_args],
171-
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
172-
preexec_fn=ensure_dead_with_parent)
178+
if _system == 'Windows':
179+
# On Windows we have to use pg_ctl as direct execution
180+
# of postgres daemon under an Administrative account
181+
# is not permitted and there is no easy way to drop
182+
# privileges.
183+
process = subprocess.run(
184+
[self._pg_ctl, 'start', '-D', self._data_dir,
185+
'-o', ' '.join(extra_args)],
186+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
187+
stderr = process.stderr
188+
189+
if process.returncode != 0:
190+
raise ClusterError(
191+
'pg_ctl start exited with status {:d}: {}'.format(
192+
process.returncode, stderr.decode()))
193+
else:
194+
self._daemon_process = \
195+
subprocess.Popen(
196+
[self._postgres, '-D', self._data_dir, *extra_args],
197+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
198+
preexec_fn=ensure_dead_with_parent)
173199

174-
self._daemon_pid = self._daemon_process.pid
200+
self._daemon_pid = self._daemon_process.pid
175201

176202
self._test_connection(timeout=wait)
177203

@@ -291,11 +317,16 @@ def add_hba_entry(self, *, type='host', database, user, address=None,
291317

292318
def trust_local_connections(self):
293319
self.reset_hba()
294-
self.add_hba_entry(type='local', database='all',
295-
user='all', auth_method='trust')
320+
321+
if _system != 'Windows':
322+
self.add_hba_entry(type='local', database='all',
323+
user='all', auth_method='trust')
296324
self.add_hba_entry(type='host', address='127.0.0.1/32',
297325
database='all', user='all',
298326
auth_method='trust')
327+
self.add_hba_entry(type='host', address='::1/128',
328+
database='all', user='all',
329+
auth_method='trust')
299330
status = self.get_status()
300331
if status == 'running':
301332
self.reload()
@@ -367,8 +398,9 @@ def _test_connection(self, timeout=60):
367398

368399
try:
369400
con = loop.run_until_complete(
370-
asyncpg.connect(database='postgres', timeout=5,
371-
loop=loop, **self._connection_addr))
401+
asyncpg.connect(database='postgres',
402+
timeout=5, loop=loop,
403+
**self._connection_addr))
372404
except (OSError, asyncio.TimeoutError,
373405
asyncpg.CannotConnectNowError,
374406
asyncpg.PostgresConnectionError):
@@ -409,11 +441,13 @@ def _find_pg_config(self, pg_config_path):
409441
if pg_config_path is None:
410442
pg_install = os.environ.get('PGINSTALLATION')
411443
if pg_install:
412-
pg_config_path = os.path.join(pg_install, 'pg_config')
444+
pg_config_path = platform_exe(
445+
os.path.join(pg_install, 'pg_config'))
413446
else:
414447
pathenv = os.environ.get('PATH').split(os.pathsep)
415448
for path in pathenv:
416-
pg_config_path = os.path.join(path, 'pg_config')
449+
pg_config_path = platform_exe(
450+
os.path.join(path, 'pg_config'))
417451
if os.path.exists(pg_config_path):
418452
break
419453
else:
@@ -434,7 +468,7 @@ def _find_pg_binary(self, binary):
434468
'could not find {} executable: '.format(binary) +
435469
'pg_config output did not provide the BINDIR value')
436470

437-
bpath = os.path.join(bindir, binary)
471+
bpath = platform_exe(os.path.join(bindir, binary))
438472

439473
if not os.path.isfile(bpath):
440474
raise ClusterError(

asyncpg/connection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,10 @@ async def cancel():
410410
try:
411411
w.write(msg)
412412
await r.read() # Wait until EOF
413+
except ConnectionResetError:
414+
# On some systems Postgres will reset the connection
415+
# after processing the cancellation command.
416+
pass
413417
except Exception as ex:
414418
waiter.set_exception(ex)
415419
finally:

asyncpg/protocol/coreproto.pyx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,9 @@ cdef class CoreProtocol:
645645
self.transport = transport
646646

647647
sock = transport.get_extra_info('socket')
648-
if sock is not None and sock.family != socket.AF_UNIX:
648+
if (sock is not None and
649+
(not hasattr(socket, 'AF_UNIX')
650+
or sock.family != socket.AF_UNIX)):
649651
sock.setsockopt(socket.IPPROTO_TCP,
650652
socket.TCP_NODELAY, 1)
651653

asyncpg/protocol/hton.pxd

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@
88
from libc.stdint cimport int16_t, int32_t, uint16_t, uint32_t, int64_t, uint64_t
99

1010

11-
cdef extern from "arpa/inet.h":
12-
uint32_t htonl(uint32_t hostlong)
13-
uint16_t htons(uint16_t hostshort)
14-
uint32_t ntohl(uint32_t netlong)
15-
uint16_t ntohs(uint16_t netshort)
11+
IF UNAME_SYSNAME == "Windows":
12+
cdef extern from "winsock2.h":
13+
uint32_t htonl(uint32_t hostlong)
14+
uint16_t htons(uint16_t hostshort)
15+
uint32_t ntohl(uint32_t netlong)
16+
uint16_t ntohs(uint16_t netshort)
17+
ELSE:
18+
cdef extern from "arpa/inet.h":
19+
uint32_t htonl(uint32_t hostlong)
20+
uint16_t htons(uint16_t hostshort)
21+
uint32_t ntohl(uint32_t netlong)
22+
uint16_t ntohs(uint16_t netshort)
1623

1724

1825
cdef inline void pack_int16(char* buf, int16_t x):

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import os
99
import os.path
10+
import platform
1011
import re
1112
import sys
1213

@@ -21,6 +22,9 @@
2122
CFLAGS = ['-O2']
2223
LDFLAGS = []
2324

25+
if platform.uname().system == 'Windows':
26+
LDFLAGS.append('ws2_32.lib')
27+
2428

2529
class build_ext(_build_ext.build_ext):
2630
user_options = _build_ext.build_ext.user_options + [

tests/test_connect.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
import contextlib
99
import ipaddress
1010
import os
11+
import platform
1112
import unittest
1213

1314
import asyncpg
1415
from asyncpg import _testbase as tb
1516
from asyncpg.connection import _parse_connect_params
1617

18+
_system = platform.uname().system
19+
1720

1821
class TestSettings(tb.ConnectedTestCase):
1922

@@ -47,13 +50,20 @@ def setUp(self):
4750
' PASSWORD {!r}'.format(password) if password else ''
4851
)
4952
)
53+
54+
if _system != 'Windows':
55+
self.cluster.add_hba_entry(
56+
type='local',
57+
database='postgres', user='{}_user'.format(method),
58+
auth_method=method)
59+
5060
self.cluster.add_hba_entry(
51-
type='local', address=ipaddress.ip_network('127.0.0.0/24'),
61+
type='host', address=ipaddress.ip_network('127.0.0.0/24'),
5262
database='postgres', user='{}_user'.format(method),
5363
auth_method=method)
5464

5565
self.cluster.add_hba_entry(
56-
type='host', address=ipaddress.ip_network('127.0.0.0/24'),
66+
type='host', address=ipaddress.ip_network('::1/128'),
5767
database='postgres', user='{}_user'.format(method),
5868
auth_method=method)
5969

tests/test_pool.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66

77

88
import asyncio
9+
import platform
910

1011
from asyncpg import _testbase as tb
1112

1213

14+
_system = platform.uname().system
15+
16+
1317
class TestPool(tb.ConnectedTestCase):
1418

1519
async def test_pool_01(self):
@@ -101,13 +105,19 @@ async def test_pool_auth(self):
101105

102106
self.cluster.reset_hba()
103107

108+
if _system != 'Windows':
109+
self.cluster.add_hba_entry(
110+
type='local',
111+
database='postgres', user='pooluser',
112+
auth_method='md5')
113+
104114
self.cluster.add_hba_entry(
105-
type='local',
115+
type='host', address='127.0.0.1/32',
106116
database='postgres', user='pooluser',
107117
auth_method='md5')
108118

109119
self.cluster.add_hba_entry(
110-
type='host', address='127.0.0.0/32',
120+
type='host', address='::1/128',
111121
database='postgres', user='pooluser',
112122
auth_method='md5')
113123

0 commit comments

Comments
 (0)