Skip to content

Commit

Permalink
release 0.0.207 (#27)
Browse files Browse the repository at this point in the history
* add auto reload support

* fix python tests below 3.8

* remove --reuse-port, as it's enabled by default

* add timeout option in tasks.create

---------

Co-authored-by: nggit <[email protected]>
  • Loading branch information
nggit and nggit committed Nov 7, 2023
1 parent a70be32 commit cb169e3
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 81 deletions.
1 change: 1 addition & 0 deletions alltests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
kwargs=dict(host=HTTP_HOST,
port=HTTP_PORT,
debug=False,
reload=True,
client_max_body_size=73728))
)
processes.append(mp.Process(
Expand Down
14 changes: 7 additions & 7 deletions example_uvloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

import asyncio

try:
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
print('INFO: uvloop is not installed')

import tremolo


Expand All @@ -22,11 +29,4 @@ async def app(scope, receive, send):
})

if __name__ == '__main__':
try:
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
print('INFO: uvloop is not installed')

tremolo.run(app, host='0.0.0.0', port=8000, debug=True)
8 changes: 1 addition & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

from setuptools import setup

if __name__ == '__main__':
import sys

if len(sys.argv) == 1:
sys.argv.append('install')

with open('README.md', 'r') as f:
long_description = f.read()

setup(
name='tremolo',
version='0.0.205',
version='0.0.207',
license='MIT',
author='nggit',
author_email='[email protected]',
Expand Down
16 changes: 7 additions & 9 deletions tests/asgi_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

__all__ = ('app', 'ASGI_HOST', 'ASGI_PORT')

import asyncio # noqa: E402
import os # noqa: E402
import sys # noqa: E402

Expand All @@ -16,7 +15,13 @@

from tests.http_server import HTTP_PORT, TEST_FILE # noqa: E402

ASGI_HOST = '::'
if sys.version_info[:2] < (3, 8):
# on Windows, Python versions below 3.8 don't properly support
# dual-stack IPv4/6. https://github.com/python/cpython/issues/73701
ASGI_HOST = '0.0.0.0'
else:
ASGI_HOST = '::'

ASGI_PORT = HTTP_PORT + 10


Expand Down Expand Up @@ -116,11 +121,4 @@ async def app(scope, receive, send):
})

if __name__ == '__main__':
try:
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
print('INFO: uvloop is not installed')

tremolo.run(app, host=ASGI_HOST, port=ASGI_PORT, debug=True, worker_num=2)
14 changes: 13 additions & 1 deletion tests/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,17 @@ async def timeouts(request=None, **_):
# should raise a TimeoutError and ended up with a RequestTimeout
await request.recv(100)


@app.route('/reload')
async def reload(request=None, **_):
yield b'%d' % hash(app)

if request.query_string != b'':
mtime = float(request.query_string)

# simulate a code change
os.utime(TEST_FILE, (mtime, mtime))

# test multiple ports
app.listen(HTTP_PORT + 1, request_timeout=2, keepalive_timeout=2)
app.listen(HTTP_PORT + 2)
Expand All @@ -279,6 +290,7 @@ async def timeouts(request=None, **_):
app.listen('tremolo-test', debug=False, client_max_body_size=73728)

if __name__ == '__main__':
app.run(HTTP_HOST, port=HTTP_PORT, debug=True, client_max_body_size=73728)
app.run(HTTP_HOST, port=HTTP_PORT, debug=True, reload=True,
client_max_body_size=73728)

# END
45 changes: 40 additions & 5 deletions tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,11 +475,17 @@ def test_headertoolarge(self):
self.assertEqual(data, (b'', b''))

def test_requesttimeout(self):
data = getcontents(
host=HTTP_HOST,
port=HTTP_PORT + 1,
raw=b'GET / HTTP/1.1\r\nHost: localhost:%d\r\n' % (HTTP_PORT + 1)
)
for _ in range(10):
try:
data = getcontents(
host=HTTP_HOST,
port=HTTP_PORT + 1,
raw=b'GET / HTTP/1.1\r\n'
b'Host: localhost:%d\r\n' % (HTTP_PORT + 1)
)
break
except ConnectionResetError:
continue

self.assertEqual(data, (b'', b''))

Expand Down Expand Up @@ -740,6 +746,34 @@ def test_websocket(self):

self.assertEqual(payload[:7], data_out[:7])

def test_reload(self):
header, body1 = getcontents(host=HTTP_HOST,
port=HTTP_PORT,
method='GET',
url='/reload?%f' % time.time(),
version='1.0')

self.assertFalse(body1 == b'')

for _ in range(10):
time.sleep(1)

try:
header, body2 = getcontents(host=HTTP_HOST,
port=HTTP_PORT,
method='GET',
url='/reload',
version='1.0')
except ConnectionResetError:
continue

self.assertFalse(body2 == b'')

if body2 != body1:
break

self.assertFalse(body2 == body1)


if __name__ == '__main__':
mp.set_start_method('spawn')
Expand All @@ -749,6 +783,7 @@ def test_websocket(self):
kwargs=dict(host=HTTP_HOST,
port=HTTP_PORT,
debug=False,
reload=True,
client_max_body_size=73728)
)

Expand Down
10 changes: 5 additions & 5 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ def getcontents(
family = socket.AF_INET

if ':' in host:
if host == '::':
host = '127.0.0.1'
else:
family = socket.AF_INET6
family = socket.AF_INET6

if host in ('0.0.0.0', '::'):
host = 'localhost'

with socket.socket(family, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(5)
sock.settimeout(10)

while sock.connect_ex((host, port)) != 0:
time.sleep(1)
Expand Down
2 changes: 1 addition & 1 deletion tremolo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.0.205'
__version__ = '0.0.207'

from .tremolo import Tremolo # noqa: E402
from . import exceptions # noqa: E402,F401
Expand Down
7 changes: 4 additions & 3 deletions tremolo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
print(' --bind Address to bind.')
print(' Instead of using --host or --port')
print(' E.g. "127.0.0.1:8000" or "/tmp/file.sock"') # noqa: E501
print(' --reuse-port Use SO_REUSEPORT when available')
print(' --worker-num Number of worker processes. Defaults to 1') # noqa: E501
print(' --backlog Maximum number of pending connections') # noqa: E501
print(' Defaults to 100')
Expand All @@ -33,7 +32,9 @@
print(' E.g. "/path/to/privkey.pem"')
print(' --debug Enable debug mode.')
print(' Intended for development')
print(' --no-ws Disable built-in WebSocket support.') # noqa: E501
print(' --reload Enable auto reload on code changes.') # noqa: E501
print(' Intended for development')
print(' --no-ws Disable built-in WebSocket support') # noqa: E501
print(' --log-level Defaults to "DEBUG". See')
print(' https://docs.python.org/3/library/logging.html#levels') # noqa: E501
print(' --download-rate Limits the sending speed to the client') # noqa: E501
Expand All @@ -53,7 +54,7 @@
sys.exit()
elif sys.argv[i - 1] == '--no-ws':
options['ws'] = False
elif sys.argv[i - 1] in ('--debug', '--reuse-port'):
elif sys.argv[i - 1] in ('--debug', '--reload'):
options[sys.argv[i - 1].lstrip('-').replace('-', '_')] = True
elif sys.argv[i - 1] in ('--host',
'--log-level',
Expand Down
3 changes: 2 additions & 1 deletion tremolo/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def connection_made(self, transport):

def connection_lost(self, exc):
if self._middlewares['close']:
self.loop.create_task(self._connection_lost(exc))
task = self.loop.create_task(self._connection_lost(exc))
self.loop.call_at(self.loop.time() + 30, task.cancel)
else:
super().connection_lost(exc)

Expand Down
6 changes: 3 additions & 3 deletions tremolo/lib/http_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,10 @@ async def sendfile(
self._request.context.RESPONSE_SENDFILE_HANDLE.close
)

file_size = os.stat(path).st_size
mtime = os.path.getmtime(path)
st = os.stat(path)
file_size = st.st_size
mdate = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
time.gmtime(mtime)).encode('latin-1')
time.gmtime(st.st_mtime)).encode('latin-1')

if (self._request.version == b'1.1' and
b'range' in self._request.headers):
Expand Down
9 changes: 7 additions & 2 deletions tremolo/lib/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ def __init__(self, tasks, loop=None):
self._loop = loop
self._tasks = tasks

def create(self, coro):
def create(self, coro, timeout=0):
task = self._loop.create_task(coro)
self._tasks.append(task.cancel)

if timeout > 0:
self._loop.call_at(self._loop.time() + timeout, task.cancel)
else:
# until the connection is lost
self._tasks.append(task.cancel)

return task
Loading

0 comments on commit cb169e3

Please sign in to comment.