Repository navigation
Object dispatch not working #13
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
I may be doing something wrong, but can't get the object dispatching to work
(see Mapping python collections builtin methods to REST API patterns).
See the code in py2http/examples/object_dispatch.py:
backend_store = {
'some_key': b'some_value',
'some_other_key': b'some_other_value',
}
handlers = [
dict(
endpoint=backend_store,
name='backend_store',
attr_names=['__iter__', '__getitem__', '__setitem__'],
),
]
from py2http import mk_app
app = mk_app(handlers, publish_openapi=True, publish_swagger=True)
if __name__ == '__main__':
app.run()Running this:
python py2http/examples/object_dispatch.py
Bottle v0.12.25 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8080/
Hit Ctrl-C to quit.
And trying it with
import requests
requests.post(
'http://localhost:3030/backend_store',
json={
"_attr_name": "__iter__",
"kwargs": {}
}
)I get:
ConnectionError: HTTPConnectionPool(host='localhost', port=3030): Max retries exceeded with url: [/backend_store](https://file+.vscode-resource.vscode-cdn.net/backend_store) (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x109025ed0>: Failed to establish a new connection: [Errno 61] Connection refused'))
Or:
curl -X 'POST' \
'http://localhost:3030/backend_store' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"_attr_name": "__iter__",
"kwargs": {}
}'
I get
curl: (7) Failed to connect to localhost port 3030 after 4 ms: Couldn't connect to server
Yet the server seems to be responding:
python py2http/examples/object_dispatch.py
Bottle v0.12.25 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8080/
Hit Ctrl-C to quit.
127.0.0.1 - - [22/Feb/2024 08:27:40] "GET /swagger HTTP/1.1" 200 1173
127.0.0.1 - - [22/Feb/2024 08:27:40] "GET /swagger/swagger.json HTTP/1.1" 200 553
127.0.0.1 - - [22/Feb/2024 08:38:48] "GET /swagger HTTP/1.1" 200 1173
127.0.0.1 - - [22/Feb/2024 08:38:48] "GET /swagger/swagger.json HTTP/1.1" 200 553
Swagger's message is:
Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be "http" or "https" for CORS request.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
thorwhalen commentedon Feb 22, 2024
The problem may not be with object dispatch but something else, since web services that worked before don't any more.
For example, getting this:
with another one.
thorwhalen commentedon Feb 22, 2024
Okay, so there's layers of things happening here, some that I don't understand.
What I don't understand
unpredictable backend server choice
Sometimes py2http chooses to use
WSGIRefServerinstead ofGunicornServer-- unpredictably, for me.Swagger works... sometimes
Swagger works... and the doesn't anymore (I start getting those "CORS or Network failure or URL scheme" errors, and then they stick, even after relaunching the server.
Now, even while swagger doesn't work, I still get the curl and requests to work.
This is not always the case, but that's because Swagger's message is catchall. The errors I was getting before, I was getting them simply because there was a network error (see later).
What I DO understand
Swagger curl pointing to wrong url
So, when (for reasons I do not know) py2http chooses to use
WSGIRefServer, it listens on 8080.To go to swagger, I also use 8080 -- but when I execute, swagger displays a curl that uses 3030.
So of course, it fails with the catchall (unhelpful) error message.
Argument type
I used:
because it's the example I found, and I assumed that one was supposed to specify all arguments in a
kwargsdict,which is empty here since
__iter__has no arguments. This is not how it works.Instead, I should be specifying the keyword arguments at the same level as the "_attr_name" field.
So I should be doing:
or
Argument type
But we still have a problem.
We'd like to do
but actually
dict.__getitem__has only one, positional-only, argument.So: Handling position-only arguments issue.
Output types
There were two more problems with py2http/examples/object_dispatch.py code.
One is that the values of the
backend_storewere bytes: This causes problems when doing a__getitem__since it's not JSON serializable.The second is that the
__iter__return type is a generator, also not serializable.Both of these things can be handled at the egress level (output_trans), but since we also have the problem of the positional-only, we can also just wrap the dict.
So we can change our previous code to this new code:
Of course, if not explicitly specified, we still can't predict what the root url will be (depends on the unpredictable server that will be chosen), but here's some code to test the service above:
thorwhalen commentedon Sep 22, 2026
Verified still live against current
master(ee5b962). Root cause: inpy2http/service.py,_get_func_to_dispatch_handlerbuilds the object-dispatch wrapper asfunc(_attr_name=None, **kwargs)and_get_attr_valuefinally callsattr(**kwargs)(service.py:589-632). Since the request body is always turned into keyword arguments, any dunder/builtin method whose parameters are positional-only (e.g.dict.__getitem__(self, key, /),__setitem__) fails withTypeError: __getitem__() takes no keyword arguments. This is the same root cause as #14 — fixing one fixes both. A fix would need_get_attr_value(or the wrapper) to inspect the target callable's signature and pass POSITIONAL_ONLY parameters positionally (py2http already has the POSITIONAL_ONLY/KEYWORD_ONLY plumbing inpy2http/types.pyandpy2http/util.pyfor the opposite direction — py→http — so the introspection primitives exist, just not wired into the http→object-attr call path).Call positional-only parameters positionally, by name (#14) (#33)
thorwhalen commentedon Oct 13, 2026
The positional-only half of this (
attr(**kwargs)on__getitem__/__setitem__) is fixed by #33 (closes #14). The gunicorn worker timeout reported here was not reproduced and is not addressed, so this stays open.object_dispatch example: serve with run_app so the port matches the O…
thorwhalen commentedon Oct 20, 2026
Diagnosis. Two of the reported symptoms were one cause: the example ran the app with bottle's own
app.run()(wsgiref, port 8080), while the generated OpenAPI spec nameshttp://localhost:3030(whererun_appserves, with gunicorn). So Swagger's "Try it out" and its curl hit a port nothing listened on. Fixed in #48: the example usesrun_app, and the spec's default server URL is derived from the default config so the two cannot drift. Positional-only dunders were fixed by #33. Still open: the gunicornWORKER TIMEOUTon one of the reporter's other services, which I could not reproduce and which has no repro in the thread; leaving this open for a repro (the service and theserver=setting would help).