Skip to content

Commit

Permalink
refactor __globals__ (#42)
Browse files Browse the repository at this point in the history
Co-authored-by: nggit <[email protected]>
  • Loading branch information
nggit and nggit authored Nov 7, 2024
1 parent 76aa909 commit 5222ac3
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 86 deletions.
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

0 comments on commit 5222ac3

Please sign in to comment.