-
Notifications
You must be signed in to change notification settings - Fork 94
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
Add async networking libraries support #210
Conversation
Albo90
commented
Mar 9, 2022
•
edited by phanak-sap
Loading
edited by phanak-sap
- Part of feature: Add async networking libraries support #208
- intended to be merged to feature-branch only at the moment
feat: add support for async http library aiohttp
Async python no duplicate code
I did checkout your code and try to run the following easy one async request example, modified from the generic sample from the issue record:
Got following error:
which is from this PR code snippet
|
Syntax is different. I posted an example into issue yesterday, and I explained like syntax is different from sync version. In my opinion the main change is the getattr use because we can't transform getattribute as async without huge and maybe dangerous impact. Your example with correct syntax: import asyncio
import aiohttp
from pyodata.client import Client
async def main():
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
async with aiohttp.ClientSession() as client:
northwind = await Client.build_async_client(SERVICE_URL, client)
order = await northwind.entity_sets.Order_Details.get_entity(OrderID=10248, ProductID=42).async_execute()
print(await order.getattr('Quantity'))
async_entities = asyncio.run(main()) |
Oh, I see my error, I forgot to change the Wonder if we could somehow distinguish correctly what kind of client instance was passed and perhaps throw an exception if execute() (and other methods intended for standard usage) is called with async client and vice-versa. If it is just the difference between pyodata.Client and pyodata.Client() constructor call versus pyodata.Client.build_async_client() then it should be easy. |
pyodata/v2/response.py
Outdated
|
||
|
||
class Response: | ||
"""Representation of http response in a standard form already used by handlers""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Odata v2 has both JSON and XML Atom as standard response...
https://www.odata.org/documentation/odata-version-2-0/atom-format/
This stuff is already handled in ODataHttpResponse class. Am I missing something why this module needs to be created as well?
pyodata/v2/service.py
Outdated
@@ -858,6 +889,19 @@ def __getattr__(self, attr): | |||
raise AttributeError('EntityType {0} does not have Property {1}: {2}' | |||
.format(self._entity_type.name, attr, str(ex))) | |||
|
|||
async def getattr(self, attr): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this method should have async_ prefix as well, similar to other new ones.
Will throw RuntimeError when called in synchronous script, where it looks like correct usage until seen the pyodata source code I guess.
import requests
import pyodata
import logging
logging.basicConfig(level=logging.DEBUG)
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
northwind = pyodata.Client(SERVICE_URL, requests.Session())
order = northwind.entity_sets.Order_Details.get_entity(OrderID=10248, ProductID=42).execute()
print(order.getattr('Quantity'))
<coroutine object EntityProxy.getattr at 0x000002032DFD7CA0>
...: RuntimeWarning: coroutine 'EntityProxy.getattr' was never awaited
print(order.getattr('Quantity'))
@Albo90 Totally lame question (async python knowledge) - could you please provide sample script where the new async You can modify your example hardcoded against Nortwind, with both getters being used:
|
I answer you about async and coroutine, tomorrow I send you more details for each point. For me the most important information about async is to understand that an async method cannot be used like sync otherwise the result is "RuntimeWarning: coroutine". For that reason getattr is only for async and we didn't use your getattribute, it's a python standard method and it's sync, if we changed it with async (I think it's impossible but I use it to explain you) you standard code doesn't work . |
Yeah I run into "RuntimeWarning: coroutine" when playing with your code and I understand why is that. I will be happy for any answer of course, but it is valid "just read PEP 492 and asyncio/aiohttp documentation" also :) So far it seems the new stuff from this PR is not affecting the old, so no backward incompatible changes for standard, synchronous usage of pyodata. That's is the main focus for the feature in fact, no breaking of existing code out there. Pyodata should remain working correctly with synchronous and standard multithreading usage. I asked basically for a simplest snippet possible for documentation, I know that little bit early on. |
In the evening we will add these feature:
For the async argument, I attach an example: import asyncio
import aiohttp
from pyodata.client import Client
async def main():
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
async with aiohttp.ClientSession() as client:
northwind = await Client.build_async_client(SERVICE_URL, client)
orders = await northwind.entity_sets.Order_Details.get_entities().select('OrderID,ProductID').async_execute()
for order in orders:
# quantity = await order.getattr('Quantity')
quantity = order.Quantity
print(quantity)
async_entities = asyncio.run(main()) An async method like getattr need "await" when it is called. If you don't set it, you create only coroutine. The coroutine cannot be print or interrogate, it can be only executed with await. The problem is your implementantion of __ getattribute __ uses cache to permit for example "order.Quantity" when you use async it could have a problem so we defined getattr, if you want I can make an example where "order.Quantity" doesn't work but "await order.getattr("Quantity")" works. |
Hi @Albo90 I am sorry, we have not yet enabled Github Actions for this feature branch as well. At the moment they run against master PRs only. But all your new tests are failing (my laptop as a CI server :) ). You are perhaps using libraries that are part of your system PIP installed packages, but missing that in the PR code. Please use following (testing) workflow for your local work:
There is a Makefile with all targets that can be used as well, if you are fan of makefiles. Errors I see with the checkout of currently latest commit a4cc84d:
Even if I install pytest-asyncio plugin as a naive solution, the tests are still failing:
|
@Albo90 please agree to the CLA - see https://github.com/SAP/python-pyodata/blob/master/CONTRIBUTING.md#developer-certificate-of-origin-dco |
dev-requirements.txt
Outdated
@@ -1,4 +1,6 @@ | |||
requests==2.23.0 | |||
pytest-asyncio == 0.15.1 | |||
aiohttp==3.8.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have already updated as first commit to the async-feature branch the dev-dependency.txt pytest-aiohttp>=1.0.4
.
Wouldn't explicit pytest plugin by better than generic aiohttp library? Just asking what dependency is better, doesn't seem right to have both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still have same 8 errors running pytest TypeError: sleep() got an unexpected keyword
even with the newly added dev-requirements.
Everything is passing in your local environment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have errors in our test, I forgot the requirements. We added our dev-requirements.txt. We don't have aiohttp test because we want to stay generic without aiohttp dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must say I am not understanding the last comment at all.
-
what does "We don't have aiohttp test because we want to stay generic without aiohttp dependency." mean? Especially when you commited
aiohttp==3.8.1
to the dev-dependencies already? Do you want that or not? -
And should the
pytest-aiohttp
from me, which already is in theasync-feature
branch dev-requirements.txt stay or not? Should the async tests be based on pytest-aiohttp plugin or pytest-asyncio +aiohttp ? I expected we will go pytest plugins way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if we were not clear, I try to summarize our considerations why they led me not to choose pytest-aiohttp:
- rewrite the tests faster and easier in case you want to choose to use another type library that uses async like httpx, this we meant when we said we didn't want to depend on aiohttp for testing
- the tests work with Application and you have problems using the get method passing all the complete url. In fact we simply had to replace SERVICE_URL with an empty string.
We have rewritten the tests anyway using pytest-aiohttp but we remain of the idea that it is more useful to use pytest-asyncio + aiohttp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: is your code in client.py/service.py compatible with httpx
python package?
Or does it expect aiohttp.ClientSession()
to be provided in the Client.build_async_client
only?
If it is more open (best possible outcome, I don't actually know about any other than requests
library being used with pyodata, see for example #154 (comment) ), then why not add both integration tests with aiohttp
and httpx
packages?
Another possibility is that you are ONLY talking about the aiohttp usage in the tests. But then I am still curious about the pyodata package (with this PR) compatibility matrix with async python http networking libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check, in theory it should work with httpx as well.
b6de2ca
to
a4cc84d
Compare
rewrite tests using pytest-aiohttp
feat: update async_client tests
tests/conftest.py
Outdated
@@ -1,4 +1,5 @@ | |||
"""PyTest Fixtures""" | |||
import asyncio |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are importing direct asyncio package, while in dev dependencies is pytest-asyncio plugin.
Therefore tests are still failing.
Btw, why you prefer the asyncio/aiohttp directly as a dependency and not the pytest plugins, if it is used for testing purposes only anyway? My guess would be that pytest plugin are used by many and doing IMHO the same stuff you are doing by hand - according to readme of both plugins. In this case I think is best to use standard solution of the problem instead of own implementation. I it simple code at the moment, I know, no actual problem merging as is, by me. Just a open discussion.
I have read the comment "we remain of the idea that it is more useful to use pytest-asyncio + aiohttp" - response is there. But on separate topic just asyncio vs pytest-asyncio - it is anyway just about providing basic wrappers around event loop in the pytest fixtures, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We forgot to update dev-requirements.txt now you shouldn't have any problems with the test anymore. We have removed some useless code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, with 3bab585 I have passing tests, only with one warning.
pytest_aiohttp\plugin.py:28: DeprecationWarning: The 'asyncio_mode' is 'legacy', switching to 'auto' for the sake of pytest-aiohttp backward compatibility. Please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file.
config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================== 250 passed, 1 warning in 3.55s
|
||
assert isinstance(service_client, pyodata.v2.service.Service) | ||
|
||
# one more test for '/' terminated url |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mark at least as TODO to grab more attention in future PR if not done.
add pytest-aiohttp remove pytest-asyncio remove aiohttp
feat: update dev-requirements.txt
No duplication v1
At the moment this looks good, tests are passing and anybody can play with it in the feature branch. Merging to |