-
Notifications
You must be signed in to change notification settings - Fork 39
Implement asyncio support, plus session optimizations #87
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
base: master
Are you sure you want to change the base?
Implement asyncio support, plus session optimizations #87
Conversation
…ll supported python interpreters.
@rubydog I'd appreciate a review when you have time, this is a major blocker for my company's onboarding. Here is the test output below:
|
Hi @seandstewart, thanks for the PR. I will take a look at it and try to get back to you by next week. Cheers |
def initialize(self): | ||
raise NotImplementedError() | ||
|
||
def teardown(self): | ||
raise NotImplementedError() |
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.
Introduced an initializer method and a clean up method - these should be called by the developer at app startup and app shutdown.
def _get(self, url: str, query: QueryT | None = None): | ||
""" | ||
Wrapper for the HTTP Request, | ||
Rate Limit Backoff is handled here, | ||
Responses are Processed with ResourceBuilder. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def _cache_content_types(self): | ||
""" | ||
Updates the Content Type Cache. | ||
""" | ||
|
||
raise NotImplementedError() |
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.
Implementation details handled by subclasses.
@property | ||
def client_info(self) -> ClientInfo: | ||
if self._client_info is None: | ||
self._client_info = self._get_client_info() | ||
|
||
return self._client_info | ||
|
||
@property | ||
def headers(self) -> dict[str, str]: | ||
if self._headers is None: | ||
self._headers = self._request_headers() | ||
|
||
return self._headers | ||
|
||
@property | ||
def proxy_info(self) -> abstract.ProxyInfo: | ||
if self._proxy_info is None: | ||
self._proxy_info = self._proxy_parameters() | ||
|
||
return self._proxy_info |
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.
These were re-created with every request to Contentful. We're now only generating this information once, which is much more efficient.
@property | ||
def transport(self) -> abstract.AbstractTransport: | ||
if self._transport is None: | ||
self._transport = self._get_transport() | ||
|
||
return self._transport | ||
|
||
def qualified_url(self) -> str: | ||
scheme = "https://" if self.https else "http://" | ||
hostname = self.api_url | ||
if hostname.startswith("http"): | ||
scheme = "" | ||
|
||
path = f"/spaces/{self.space_id}/environments/{self.environment}/" | ||
url = f"{scheme}{hostname}{path}" | ||
return url | ||
|
||
def _get_transport(self) -> abstract.AbstractTransport: | ||
base_url = self.qualified_url() | ||
transport = self.transport_cls( | ||
base_url=base_url, | ||
timeout_s=self.timeout_s, | ||
proxy_info=self.proxy_info, | ||
default_headers=self.headers, | ||
max_retries=self.max_rate_limit_retries, | ||
max_retry_wait_seconds=self.max_rate_limit_wait, | ||
) | ||
return transport |
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 initialize the transport lazily and provide the fully-qualified URL to the targeted Contentful space/env as a base_url to the Transport.
def _format_params(self, query: QueryT | None) -> dict[str, str]: | ||
query = query or {} | ||
params = queries.normalize(**query) | ||
if not self.authorization_as_header: | ||
params["access_token"] = self.access_token | ||
|
||
return params |
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.
Isolated query-string formatting into a more efficient, generic normalizer.
__all__ = ( | ||
"Client", | ||
"AsyncClient", | ||
"Entry", | ||
"Asset", | ||
"Space", | ||
"Locale", | ||
"Link", | ||
"ContentType", | ||
"DeletedAsset", | ||
"DeletedEntry", | ||
"ContentTypeCache", | ||
"ContentTypeField", | ||
) | ||
|
||
_metadata = metadata.metadata(__package__) | ||
|
||
__version__ = _metadata.get("version") | ||
__author__ = _metadata.get("author") | ||
__email__ = _metadata.get("author-email") |
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.
Defining an __all__
and using importlib.metadata
to get package metadata.
) | ||
logger.debug( | ||
f"{prefix}{retry_message}", | ||
extra={"tries": tries, "reset_time": reset_time}, |
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.
Passing in extra=
will work well with structured logging for app developers.
def update_cache(cls, *, space_id: str, content_types: list[ContentType]): | ||
""" | ||
Updates the Cache with all Content Types from the Space. | ||
""" | ||
|
||
cls.__CACHE__[client.space_id] = client.content_types() | ||
cls.__CACHE__[space_id] = content_types |
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.
Breaking change: Can't implicitly call the client - interface now requires that you call the client and pass in the result.
tests/test_async_client.py
Outdated
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.
Copied from test_client
and updated for async.
error = errors.get_error_for_status_code( | ||
response.status_code, | ||
content=response.content, | ||
headers=response.headers, | ||
) |
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.
Only major update was to migrate to the new function.
@rubydog Sounds good. I've gone through and added a self-review! |
Hello, any status update for this PR? |
Hi @pdelagrave, We are currently discussing internally whether we want to support asyncio. Given the scope of this PR, it will take me some time to review it thoroughly. Apologies for the delay. |
This change adds support for asyncio. In order to do so, I made the following changes:
resolves #86