py2http.obj_dispatch¶
Dispatching HTTP requests to the attributes of objects, classes and modules.
A handler whose endpoint is not a function names the attributes clients may call
with attr_names (see public_attr for '*'):
a module: its functions are called by name. They are function endpoints like any other: no object is kept, so there is no size bound, and a function that mutates module globals does so for every client, exactly as a plain function endpoint (or a bound method passed into an app spec) would. Authenticate with the auth middleware (
mk_jwt_middleware,JWTPlugin), or pass an optionalobj_ownerto refuse requests it does not identify;an instance (one object every client shares):
_instance_endpoint_dispatcher;a class (each client creates and uses its own objects):
_class_endpoint_dispatcher.
Instances and classes need an obj_owner, a (request) -> owner hook reading
current_request, and are bounded in size (py2http.sizeof), measured after each
call except those to obj_readers. A call returning a generator, coroutine or async
generator is measured when it is returned and again when that result finishes.
Module Attributes
The request being handled (set around every handler call), so per-request hooks such as |
|
|
Functions
|
The attribute policy |
|
Dispatch ANY attribute of the object, including private ones and dunders. |
Classes
|
A mapping that keeps at most |
|
Objects kept per owner, with object and byte budgets. |
- class py2http.obj_dispatch.LRUObjStore(*, max_size=1000, on_evict=None)[source]¶
Bases:
MutableMappingA mapping that keeps at most
max_sizeitems, evicting the least recently used one when a new key would exceed it (on_evict(k, v)is told).Used as the default store of objects created through a class endpoint, so that clients cannot grow server memory without bound by creating objects.
>>> s = LRUObjStore(max_size=2) >>> s['a'], s['b'] = 1, 2 >>> _ = s['a'] # 'a' is now the most recently used >>> s['c'] = 3 >>> sorted(s) ['a', 'c']
- py2http.obj_dispatch.ObjOwner¶
(request) -> owner, a hashable that identifies an authenticated client (e.g. a user id from a verified token).Noneand""mean “not identified” and are refused. A client-chosen value (a session cookie the client mints, an IP) lets one client create owners until others are evicted.- Type:
obj_ownerhook
- class py2http.obj_dispatch.OwnedObjStore(*, quota=100, max_owners=1000)[source]¶
Bases:
objectObjects kept per owner, with object and byte budgets. Thread-safe.
quotaobjects per owner: an owner’s own least recently used object is evicted beyond it, never someone else’s;max_ownersowners at once: the least recently active owner’s objects go;byte accounting:
record()the size of an object;owner_bytes()andtotal_bytessum what is currently stored.
>>> s = OwnedObjStore(quota=2, max_owners=2) >>> s.put('ann', 'a1', 1); s.put('ann', 'a2', 2); s.put('bob', 'b1', 3) >>> s.put('ann', 'a3', 4) # ann's quota: her least recently used object goes >>> s.get('ann', 'a1') is None, s.get('ann', 'a3'), s.get('bob', 'b1') (True, 4, 3) >>> s.get('bob', 'a3') is None # another owner's id is simply not found True >>> s.record('ann', 'a3', 100); s.record('bob', 'b1', 5) >>> s.owner_bytes('ann'), s.total_bytes (100, 105)
- py2http.obj_dispatch.current_request: ContextVar = <ContextVar name='py2http_current_request' default=None>¶
The request being handled (set around every handler call), so per-request hooks such as
obj_ownercan read it without the dispatched function taking it.
- py2http.obj_dispatch.public_attr(obj, attr_name)[source]¶
The attribute policy
attr_names='*'stands for: only public methods.A name is dispatchable if it does not start with
_andfor a module: it is a function that the module defines (or lists in its
__all__), never something it merely imports, nor plain data;otherwise: it is a plain Python function,
staticmethod,classmethodorpropertydefined in the object’s class (or a base class), and the instance does not shadow it.
This keeps private helpers, dunders (
__dict__,__init__, …), per-instance state (__dict__entries,__slots__, dataclass and namedtuple fields) and callables merely assigned as class attributes (opener = open) out of reach of HTTP clients.Note that every public method is exposed, inherited ones included (e.g. a pydantic model’s
model_dump). For anything holding secrets, or a mapping whose mutators should stay private (MutableMappingmixins such asclearandpopare plain functions), list the names explicitly instead.>>> class A: ... data = 0 ... opener = open ... def f(self): ... ... def _g(self): ... ... def __init__(self): self.secret = 1 >>> [public_attr(A(), name) ... for name in ('f', '_g', '__init__', '__dict__', 'secret', 'data', 'opener')] [True, False, False, False, False, False, False]- Return type:
- py2http.obj_dispatch.unsafe_any_attr(obj, attr_name)[source]¶
Dispatch ANY attribute of the object, including private ones and dunders.
This was the behaviour of
attr_names='*'beforepublic_attr: it lets a client read__dict__, call__init__with arguments of its choosing, and so on. Useattr_names=unsafe_any_attronly when every client is trusted.- Return type: