isinstance not work correctly sometimes
Closed this issue · 9 comments
Bug report
Bug description:
PoorWSGI has OpenAPI Core wrapper: https://github.com/PoorHttp/PoorWSGI/blob/2.6.0/poorwsgi/openapi_wrapper.py#L16
class OpenAPIRequest():
...which is used in openapi example:
https://github.com/PoorHttp/PoorWSGI/blob/2.6.0/examples/openapi3.py#L80
req.api = OpenAPIRequest(req)
unmarshal_request(req.api, app.openapi_spec)
# in before_response
unmarshal_response(
req.api or OpenAPIRequest(req), # when error in before_request
OpenAPIResponse(res),
app.openapi_spec)And openapi_core do check type of request by isinstance:
https://github.com/python-openapi/openapi-core/blob/0.18.2/openapi_core/shortcuts.py#L140
which contains this:
# lines in unmarshal_request
if not isinstance(request, (Request, WebhookRequest)):
raise TypeError("'request' argument is not type of (Webhook)Request")
# lines in unmarshal_response
if not isinstance(request, (Request, WebhookRequest)):
raise TypeError("'request' argument is not type of (Webhook)Request")In openapi example, both of functions are called, first before processing request, second time after processing request. But isinstance in first time return True, which is really not right. But second time, it returns False which is right. I many-times check if really check the right thing, here is logger output which i add to unmarshal_response function:
ERROR:root:instance result: True
ERROR:root:type of request: <class 'poorwsgi.openapi_wrapper.OpenAPIRequest'>
ERROR:root:Request type: <class 'openapi_core.protocols.Request'>
ERROR:root:WebHook type: <class 'openapi_core.protocols.WebhookRequest'>
CPython versions tested on:
3.8, 3.9, 3.10, 3.11, 3.12
Operating systems tested on:
Linux
Hello.
Your code example has two same isinstance cheks, is it intentionally?
Seems you pass there not valid arguments
As I can see, OpenAPIRequest does not inherited from Request or WenhookRequest, so isinstance(OpenAPIRequest(), (Request, WebhookRequest)) returns False as expected.
Yes, but first time to calling isinstance returns True instead of False. That is the bug.
Please see my log output. And yes double checks of Request is intentional, depends on context.
Yes, but first time to calling
isinstancereturns True instead of False. That is the bug.Please see my log output
Can you write a MRE, please?
From the code I see that you inverse the result of isinstance, so False is expected here
I'm sorry for confusing report. Here is part of code, which i add (the logging) to
https://github.com/python-openapi/openapi-core/blob/0.18.2/openapi_core/shortcuts.py#L140
if not isinstance(request, (Request, WebhookRequest)):
raise TypeError("'request' argument is not type of (Webhook)Request")
import logging
logging.error("instance result: %s", isinstance(request, (Request, WebhookRequest)))
logging.error("type of request: %s", type(request))
logging.error("Request type: %s", Request)
logging.error("WebHook type: %s", WebhookRequest)And one more time the output:
ERROR:root:instance result: True
ERROR:root:type of request: <class 'poorwsgi.openapi_wrapper.OpenAPIRequest'>
ERROR:root:Request type: <class 'openapi_core.protocols.Request'>
ERROR:root:WebHook type: <class 'openapi_core.protocols.WebhookRequest'>
I saw some problems with type on Python2.7, which was depends on import, but i never see problems with isinsance. So it should be hard to create MRE.
Can you show the mro of request?
type(request).__mro__
logging.error("mro of request: %s", type(request).__mro__)ERROR:root:mro of request: (<class 'poorwsgi.openapi_wrapper.OpenAPIRequest'>, <class 'object'>)
logging.error("mro of request: %s", type(request).__mro__)ERROR:root:mro of request: (<class 'poorwsgi.openapi_wrapper.OpenAPIRequest'>, <class 'object'>)
Request type is marked as @runtime_checkable: https://github.com/python-openapi/openapi-core/blob/0.18.2/openapi_core/protocols.py#L28
So, there's no error. Here you can read what is @runtime_checkable: https://docs.python.org/3.12/library/typing.html#typing.runtime_checkable
Ohh, thanks for your time.
The problem is that in first function unmarshal_request object is instance of Response, but the same test in unmarshal_response isn't instance. Dumps looks same, i try to find out, what's happen. Somewhere there is still bug, but apparently not in isinstance, cause inheriting OpenAPIRequest(Request) helps.
Ok, i got it. And hard to say if is bug, but for my opinion, there are few bugs hidden after bad user (programmer) implementation.
Here is MRE:
import typing
import logging
@typing.runtime_checkable
class Boo(typing.Protocol):
@property
def boo(self) -> str:
...
@property
def data(self) -> str:
...
@typing.runtime_checkable
class Foo(Boo, typing.Protocol):
@property
def foo(self) -> str:
...
class MyClass():
@property
def foo(self):
logging.error("error in foo")
raise RuntimeError("No foo")
@property
def boo(self):
logging.error("error in boo")
raise RuntimeError("No boo")
@property
def data(self):
logging.error("error data")
self.nothing
return "data"
myc = MyClass()
print(isinstance(myc, Boo))Environment of bug:
- The Protocol class which is tested in isinstance must to be inherited of another Protocol class
- It must be error in access to property -> i mean reading the property (which is really error of user, not in isinstance)
What happen:
- When inherited Protocol class is checking, it probably checks it's properties (values) - that should be documented! And it happen only for attributes in base Protocol class - may be this should be bug too.
- The properties are checking in random order -> that is fine for me, but it goes to random (undefined) behavior - see output, and random behavior is bug too, but when it will be fix next bug, this is ok.
- At the end, if there is exception in reading property like in
boomethod, i see it, but if i access to not existing attribute like in data, i don't. And that is bug for my opinion.
I got these too outputs:
ERROR:root:error in boo
Traceback (most recent call last):
File "/home/mcbig/mre_isinstance.py", line 45, in <module>
print(isinstance(myc, Boo))
^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/typing.py", line 2006, in __instancecheck__
if all(hasattr(instance, attr) and
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/typing.py", line 2006, in <genexpr>
if all(hasattr(instance, attr) and
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mcbig/mre_isinstance.py", line 36, in boo
raise RuntimeError("No boo")
RuntimeError: No boo
or
ERROR:root:error data
False