Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor __globals__ #42

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions examples/__globals__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@

import asyncio
import __main__

from httpout import app, __globals__

# just for testing. the only thing that matters here is the `app` :)
assert __main__ is __globals__

# in routes it should be available as `__globals__.counter`
# you can't access this from inside the middleware, btw
Expand All @@ -24,22 +29,15 @@ async def _on_response(self, **server):
del response.headers[b'x-debug']


# you have access to the httpout's app object when
# the worker starts (`__enter__`) or ends (`__exit__`)
# allowing you to inject middlewares and etc.
def __enter__(app):
app.logger.info('entering %s', __file__)

# apply middleware
_MyMiddleware(app)
app.logger.info('entering %s', __file__)

app.ctx.sleep = asyncio.sleep(1)
# apply middleware
_MyMiddleware(app)


# `async` is also supported
async def __exit__(app):
@app.on_worker_stop
async def _on_worker_stop(**worker):
app.logger.info('exiting %s', __file__)

# incremented in `main.py`
assert counter > 0
await app.ctx.sleep
121 changes: 48 additions & 73 deletions httpout/httpout.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def __init__(self, app):

async def _on_worker_start(self, **worker):
worker_ctx = worker['context']
app = worker['app']
loop = worker['loop']
logger = worker['logger']
thread_pool_size = worker_ctx.options.get('thread_pool_size', 5)
Expand All @@ -40,29 +39,34 @@ async def _on_worker_start(self, **worker):
)
worker_ctx.options['document_root'] = document_root

logger.info('entering directory: %s', document_root)
os.chdir(document_root)
sys.path.insert(0, document_root)

# provides __globals__, a worker-level context
worker['__globals__'] = new_module('__globals__')

def wait(coro, timeout=None):
return asyncio.run_coroutine_threadsafe(coro, loop).result(timeout)

def load_module(name, globals, level=0):
if name in globals['__main__'].__server__['modules']:
if (globals['__name__'] != '__globals__' and
name in globals['__main__'].__server__['modules']):
# already imported
return globals['__main__'].__server__['modules'][name]

module = new_module(name, level, document_root)

if module:
logger.info(
'%d: %s: importing %s',
globals['__main__'].__server__['request'].socket.fileno(),
globals['__name__'],
name
)
module.__main__ = globals['__main__']
module.__server__ = globals['__main__'].__server__
module.print = globals['__main__'].print
module.run = globals['__main__'].run
module.wait = wait
globals['__main__'].__server__['modules'][name] = module
logger.info('%s: importing %s', globals['__name__'], name)

if globals['__name__'] != '__globals__':
module.__main__ = globals['__main__']
module.__server__ = globals['__main__'].__server__
module.print = globals['__main__'].print
module.run = globals['__main__'].run
module.wait = wait
globals['__main__'].__server__['modules'][name] = module

exec_module(module)
return module
Expand All @@ -71,16 +75,15 @@ def load_module(name, globals, level=0):

def ho_import(name, globals=None, locals=None, fromlist=(), level=0):
if (name not in sys.builtin_module_names and
globals is not None and '__main__' in globals and
globals['__main__'].__file__.startswith(document_root)):
globals is not None and '__file__' in globals and
globals['__file__'].startswith(document_root)):
# satisfy import __main__
if name == '__main__':
logger.info(
'%d: %s: importing __main__',
globals['__main__'].__server__['request']
.socket.fileno(),
globals['__name__']
)
logger.info('%s: importing __main__', globals['__name__'])

if globals['__name__'] == '__globals__':
return worker['__globals__']

return globals['__main__']

oldname = name
Expand All @@ -104,9 +107,12 @@ def ho_import(name, globals=None, locals=None, fromlist=(), level=0):
return module

if name == 'httpout' or name.startswith('httpout.'):
module = globals['__main__'].__server__['modules'][
globals['__name__']
]
if globals['__name__'] == '__globals__':
module = worker['__globals__']
else:
module = globals['__main__'].__server__['modules'][
globals['__name__']
]

# handles virtual imports,
# e.g. from httpout import request, response
Expand All @@ -115,20 +121,19 @@ def ho_import(name, globals=None, locals=None, fromlist=(), level=0):
if child in module.__dict__:
continue

if child in module.__server__:
module.__dict__[child] = module.__server__[
child
]
else:
if (globals['__name__'] == '__globals__' or
child not in module.__server__):
try:
module.__dict__[child] = getattr(
builtins, child
)
except AttributeError as exc:
module.__dict__[child] = worker[child]
except KeyError as exc:
raise ImportError(
f'cannot import name \'{child}\' '
f'from \'{name}\''
) from exc
else:
module.__dict__[child] = module.__server__[
child
]

return module

Expand All @@ -139,38 +144,17 @@ def ho_import(name, globals=None, locals=None, fromlist=(), level=0):
worker_ctx.wait = wait
worker_ctx.caches = {}
worker_ctx.executor = MultiThreadExecutor(thread_pool_size)

worker_ctx.executor.start()
logger.info('entering directory: %s', document_root)
os.chdir(document_root)
sys.path.insert(0, document_root)

# provides __globals__, a worker-level context
builtins.__globals__ = new_module('__globals__')
app.ctx = worker_ctx

if __globals__: # noqa: F821
exec_module(__globals__) # noqa: F821

if '__enter__' in __globals__.__dict__: # noqa: F821
coro = __globals__.__enter__(app) # noqa: F821
if worker['__globals__']:
exec_module(worker['__globals__'])

if hasattr(coro, '__await__'):
await coro
builtins.__globals__ = worker['__globals__']
else:
builtins.__globals__ = ModuleType('__globals__')

async def _on_worker_stop(self, **worker):
app = worker['app']

try:
if '__exit__' in __globals__.__dict__: # noqa: F821
coro = __globals__.__exit__(app) # noqa: F821

if hasattr(coro, '__await__'):
await coro
finally:
await app.ctx.executor.shutdown()
await worker['context'].executor.shutdown()

async def _on_request(self, **server):
request = server['request']
Expand Down Expand Up @@ -226,10 +210,8 @@ async def _on_request(self, **server):

if ext == '.py':
# begin loading the module
logger.info(
'%d: %s -> __main__: %s',
request.socket.fileno(), path, module_path
)
logger.info('%s -> __main__: %s', path, module_path)

server['request'] = HTTPRequest(request, server)
server['response'] = HTTPResponse(response)
server['REQUEST_METHOD'] = request.method.decode('latin-1')
Expand Down Expand Up @@ -264,9 +246,7 @@ async def _on_request(self, **server):
code = worker_ctx.caches.get(module_path, None)

if code:
logger.info(
'%d: %s: using cache', request.socket.fileno(), path
)
logger.info('%s: using cache', path)

try:
# execute module in another thread
Expand All @@ -277,9 +257,7 @@ async def _on_request(self, **server):

if result:
worker_ctx.caches[module_path] = result
logger.info(
'%d: %s: cached', request.socket.fileno(), path
)
logger.info('%s: cached', path)
else:
# cache is going to be deleted on @app.on_close
# but it can be delayed on a Keep-Alive request
Expand Down Expand Up @@ -330,10 +308,7 @@ async def _on_request(self, **server):
if ext not in mime_types:
raise Forbidden(f'Disallowed file extension: {ext}')

logger.info(
'%d: %s -> %s: %s',
request.socket.fileno(), path, mime_types[ext], module_path
)
logger.info('%s -> %s: %s', path, mime_types[ext], module_path)
await response.sendfile(
module_path,
content_type=mime_types[ext],
Expand Down
Loading