mikewooster / api-client Goto Github PK
View Code? Open in Web Editor NEWSeparate the high level client implementation from the underlying CRUD.
License: MIT License
Separate the high level client implementation from the underlying CRUD.
License: MIT License
Content-type: application/json OR NONE in request_formatter.py but need other Content-type such as Content-Type: application/x-www-form-urlencoded, Content-Type: text/xml, Content-Type: multipart/form-data
example header:
Host: 10.10.32.13:8222
User-Agent: python-requests/2.20.0
Accept-Encoding: gzip, deflate
Accept: /
Connection: keep-alive
apiKey: XXXXXXXXXXXXXXXXXXXXXXXXX
Content-type: application/x-www-form-urlencoded
Content-Length: 130
Is your feature request related to a problem? Please describe.
I would like to make requests without the full URL.
Describe the solution you'd like
I'd like to define a class or instance attribute base_url
that is automatically prepended to the endpoint passed into get
, post
, etc.
Describe alternatives you've considered
Considering subclassing, but it feels like this is a fundamental feature. Did I miss it in the documentation?
Describe the bug
When you install this library and google_api_python_client, the init.py is overridden by google_api_python_client, and you can't import anything from root folder
Google's one is probably here for retro compatibility, but it's conflicting with this (really generic) library's folder name.
To Reproduce
Steps to reproduce the behavior:
api-client
and google-api-python-client
api_client
Expected behavior
Import from init like in the documentation.
Screenshots
.venv/lib/python3.10/site-packages/api_client-1.3.1.dist-info/RECORD
apiclient/init.py,sha256=OG83djdXgEeSQ5gpEAHq2EjvwmBYeD3h00xwN1R2IlE,551
.venv/lib/python3.10/site-packages/google_api_python_client-2.72.0.dist-info/RECORD
apiclient/__init__.py,sha256=U1SOZ1GeeF3uCr0fzO5Od-rpe17R12-Ppyq25NTSbow,746
Versions (please complete the following information):
Additional context
Unfortunately, the only solution I see is to rename the folder...(and why not the project).
the name doesn't help to find this library, and adding some words in the name would improve SEO
(api-client-toolkit, request-api-client, rest-api-client, ...)
PS: I love what you do with this library, It makes creating rest-client so DRY!
The Bearer
part in the header Authorization: Bearer foo
is normally referred to as the scheme, not the realm.
Refer to:https://github.com/MikeWooster/api-client
from apiclient import APIClient, endpoint, paginated, retry_request
import models lost [HeaderAuthentication,JsonResponseHandler,JsonRequestFormatter] three modles that below script used in:
client = JSONPlaceholderClient(
authentication_method=HeaderAuthentication(token="<secret_value>"),
response_handler=JsonResponseHandler,
request_formatter=JsonRequestFormatter,
)
@endpoint(base_url="https://jsonplaceholder.typicode.com")
class Endpoint:
todos = "todos"
todo = "todos/{id}"
def get_next_page(response):
return {
"limit": response["limit"],
"offset": response["offset"] + response["limit"],
}
class JSONPlaceholderClient(APIClient):
@paginated(by_query_params=get_next_page)
def get_all_todos(self) -> dict:
return self.get(Endpoint.todos)
@retry_request
def get_todo(self, todo_id: int) -> dict:
url = Endpoint.todo.format(id=todo_id)
return self.get(url)
client = JSONPlaceholderClient(
authentication_method=HeaderAuthentication(token="<secret_value>"),
response_handler=JsonResponseHandler,
request_formatter=JsonRequestFormatter,
)
It is not explicit defined, but api-client is using requests.session, and **kwargs, so handling files should be available like here https://requests.readthedocs.io/en/latest/user/advanced/#post-multiple-multipart-encoded-files
My problem
src/app/client.py:5: error: Skipping analyzing "apiclient": found module but no type hints or library stubs [import]
src/app/client.py:5: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
Solution I'd like
Add a py.typed
file as per Missing type hints for third party library
Alternatives
Either ignore locally with type:ignore[import]
in line or globally with ignore_missing_imports=true
mypy flag.
Additional context
The library has fairly comprehensive types, so would be helpful to get them at the application level.
more documentation required around using get
, post
, etc.
also need more documentation around initial client setup - with links to more info sections.
Just spent a good few hours debugging an issue in a request which was reported as:
Error when contacting https://....
but was actually some AsyncIO error due to blocking calls within a async loop.
This should only catch underlying HTTP errors
Tenacity has removed tenacity.compat.make_retry_state
which features heavily in test_retrying.py
.
api-client/tests/test_retrying.py
Line 6 in 5a1370f
To Reproduce
python -m pip install .[test]
pytest
Environment
(.venv) peter@petes-laptop:~/peterschutt/api-client$ python -m pip freeze
api-client @ file:///home/peter/peterschutt/api-client
attrs==20.3.0
certifi==2020.12.5
chardet==4.0.0
coverage==5.5
idna==2.10
iniconfig==1.1.1
multidict==5.1.0
packaging==20.9
pluggy==0.13.1
py==1.10.0
pyparsing==2.4.7
pytest==6.2.2
pytest-cov==2.11.1
pytest-env==0.6.2
PyYAML==5.4.1
requests==2.25.1
requests-mock==1.8.0
six==1.15.0
tenacity==7.0.0
toml==0.10.2
urllib3==1.26.4
vcrpy==4.1.1
wrapt==1.12.1
yarl==1.6.3
I've run out of time to look at a fix right now, but will do so at my next opportunity if you haven't already resolved.
It is necessary to support asynchronous requests to use in async code.
Many REST APIs do not return a body on DELETE, otherwise return JSON.
ResponseParseError: Unable to decode response data to json. data=''
It'd be great if you could remove this print()
from the code. It's a lot of noise, specially when stdout is redirected to logs automatically, like in AWS Lambda for instance.
Edit: removed or converted to logging
.
Python 3.11, api-client 1.3.1
I am accessing a REST API where the POST data is an array of object (i.e., Sequence[dict]) - the signature of RequestStrategy.post()
only accepts dict (object), which generates type-mismatch errors in IDEs such as PyCharm:
Expected type 'dict', got 'Sequence[dict]' instead
To Reproduce
Steps to reproduce the behavior:
post()
with a list (e.g., []
)Expected behavior
Arrays are valid JSON so they should be accepted in the method signature.
Typing in Python is more advisory than enforced so this does not impact execution but can create confusion with any type-checking.
I'm creating a test using pytest and unittest.mock:
def api_mock_response() -> dict:
with open("tests/resources/response-mock.json") as f:
return json.load(f)
def test_recent_releases():
"""Tests an API call to get the latest release."""
mock_strategy = Mock(spec=BaseRequestStrategy, return_value=api_mock_response(), status_code=HTTPStatus.OK)
auth = HeaderAuthentication(parameter="X-API-Token", token=Env.api_token, scheme=None)
api = AppCenter(
owner_name=Env.owner_name,
app_name=Env.app_name,
authentication_method=auth,
response_handler=JsonResponseHandler,
request_strategy=mock_strategy,
)
response = api.get_latest_release()
assert response == appcenter_mock_response()
I am expecting the Mock strategy would respond with a fixed response coming from the api_mock_response()
function (which spits out a JSON object but I'm just getting <Mock name='mock.get()' id='12345'>
instead of the desired response. My objective is to test if I'm getting the correct response from the mocked endpoint.
Any pointers would be greatly appreciated. Thank you!
e.g. github api.
I'm fairly new to using tox in my own projects so I'd have to do some digging to work out a fix, but if I insert an explicitly failing test, the tox
command still returns successes. I think it is just building each environment but not running the tests on them.
To Reproduce
See PR #62 which I've made as demonstration that the CI tests pass.
Expected behavior
CI should fail.
I like using api-client
but find myself more often programming in async environments where IO is a feature of the work.
I notice that you have async support listed in the roadmap of the project, have you given the implementation much thought? Is it going to be a stack of work to do, or pretty reasonable? Have you got any ideas about how the work would look?
For now I'm going to be rolling my own client per app based on HTTPX I suppose, but I love consistency so if having api-client support async workflows is attainable I'd consider helping out. Interestingly asyncio-client
namespace still seems to be available on pypi:)
Is your feature request related to a problem? Please describe.
Extending a the client class with new attributes doesn't work with paginators.
I'd like to be able to extend the client class.
class My_Client(APIClient):
def __init__(self, base_url, **kwargs):
self.base_url = base_url
super().__init__(**kwargs)
Traceback (most recent call last):
File "./example.py", line 1397, in <module>
my_id=my_id))
File ".venv/lib/python3.6/site-packages/apiclient/paginators.py", line 36, in wrap
with set_strategy(client, strategy) as temporary_client:
File "/usr/lib/python3.6/contextlib.py", line 81, in __enter__
return next(self.gen)
File ".venv/lib/python3.6/site-packages/apiclient/paginators.py", line 17, in set_strategy
temporary_client = client.clone()
File ".venv/lib/python3.6/site-packages/apiclient/client.py", line 102, in clone
request_formatter=self.get_request_formatter(),
TypeError: __init__() missing 1 required positional argument: 'base_url'
Describe the solution you'd like
In client.py replace clone function with deepcopy from the standard library. I've tested this an it works.
Describe alternatives you've considered
You can do this other ways. I've extended the endpoint class, but that becomes messy as I have to pass each endpoint to the function in the client repeatedly.
Additional context
See above.
Is your feature request related to a problem? Please describe.
Ability to handle OAuth flows with secret id/key exchange for token, perhaps with refresh.
Describe the solution you'd like
a new auth class like HeaderAuthentication
/CookieAuthentication
but can take id
, key
, url
and work some magic before the request is done to get an actual token (and then use the normal HeaderAuthentication
Describe alternatives you've considered
I've made my own adapter, but it's pretty common auth method so a one liner would be nice.
Additional context
I wrote this which seems to do the trick, maybe could be adapted a bit. It does nothing in the perform_initial_auth
, instead gets the token if needed in the get_headers
call so that if the token is expired it can be refreshed first.
An alternative would be to assume one request per instance and then just have some kind of BodyAuthentication
that can send id/key in the body and collect the token from the body which is then put into the existing HeaderAuthentication
.
from time import time
from apiclient.authentication_methods import BaseAuthenticationMethod
class OAuthAuthentication(BaseAuthenticationMethod):
"""Authentication using secret_id and secret_key."""
_access_token = None
_token_expiration = None
_refresh_token = None
_refresh_expiration = None
def __init__(
self,
token_url,
refresh_url,
body,
expiry_margin=10,
):
"""Initialize OAuthAuthentication."""
self._token_url = token_url
self._refresh_url = refresh_url
self._body = body
self._expiry_margin = expiry_margin
def get_headers(self):
self.refresh_token()
return {
'Authorization': f'Bearer {self._access_token}',
}
def refresh_token(self):
if self._token_expiration and self._token_expiration >= int(time()):
return True
if self._refresh_expiration and self._refresh_expiration >= int(time()):
ret = self._client.post(
self._refresh_url,
data={
'refresh': self._refresh_token,
},
)
self._access_token = ret.get('access')
self._token_expiration = int(time()) + int(ret.get('access_expires')) - self._expiry_margin
return True
ret = self._client.post(self._token_url, data=self._body)
self._access_token = ret.get('access')
self._refresh_token = ret.get('refresh')
self._token_expiration = int(time()) + int(ret.get('access_expires')) - self._expiry_margin
self._refresh_expiration = int(time()) + int(ret.get('refresh_expires')) - self._expiry_margin
def perform_initial_auth(self, client):
self._client = client
how to get the files through the url address?
I used get or post method,but failed.would you have a example,please.
Some APis require more than one pair in header for authentication.
Would be nice, to provide more than one pair key value, eg: get them from dict.
Describe the bug
from apiclient import APIClient, endpoint, paginated, retry_request, NoAuthentication, JsonResponseHandler, JsonRequestFormatter
File "/usr/local/lib/python3.6/site-packages/api_client-1.2.0-py3.6.egg/apiclient/init.py", line 10, in
from apiclient.json import marshal_request, unmarshal_response
File "/usr/local/lib/python3.6/site-packages/api_client-1.2.0-py3.6.egg/apiclient/json.py", line 3, in
from jsonmarshal import marshal, unmarshal
File "/usr/local/lib/python3.6/site-packages/jsonmarshal-0.0.1-py3.6.egg/jsonmarshal/init.py", line 1, in
from jsonmarshal.fields import json_field
File "/usr/local/lib/python3.6/site-packages/jsonmarshal-0.0.1-py3.6.egg/jsonmarshal/fields.py", line 1, in
import dataclasses
ModuleNotFoundError: No module named 'dataclasses'
To Reproduce
I manually python setup.py install from api-client-1.2.1.tar.gz and update to api-client (1.2.0) but occur the error above. The version api-client (1.1.8) is ok.
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
Versions (please complete the following information):
Hi Mike,
I'm enjoying using this library, so thanks for your time and efforts putting it together!
Is your feature request related to a problem? Please describe.
I'd like to pass a RequestStrategy
to the client constructor.
Describe the solution you'd like
Similar to how the client accepts authentication_method
et al. as constructor params, I'd like to be able to pass a request_strategy
instance one way or another.
Describe alternatives you've considered
I construct the client and then call set_request_strategy()
on the client instance.
Additional context
Is it that the request strategy needs to be a distinct instance per client given the client is persisted on the strategy? Would you consider a parameter that accepts a callable which returns a strategy instance? It might be RequestStrategy
type or something more complicated. Or perhaps a None
default value for parameter that accepts an instance so that if nothing is passed in a fresh RequestStragegy
can be constructed for the client?
As mentioned above, in terms of LOC this is only saving 1 call to set_request_strategy()
so I'd entirely understand if you decide its not worth the effort!
Is your feature request related to a problem? Please describe.
I used CURL ad hoc COMMAND LINE to the API interface is ok,but follow the Extended Example post the same arguments failed many times. Could add some debug option before request the API interface,such as print the REQUEST CONNENTS equal to the CURL COMMAND result.
Describe the solution you'd like
Please add debug option, print the REQUEST RESULT that equal to CURL COMMAND LINE
In order to call an API with the bearer token, I need to get an /oauth/token
first.
What is your suggestion on how to accomplish this with api-client?
specifically in context that the /oauth/token
might expire and need to be renewed once the API server returns a 401.
Describe the bug
When using both JsonResponseHandler and UrlPaginatedRequestStrategy, the next_page function receives decoded JSON body as the response param, meaning it can't read the headers
To Reproduce
def header_pagination(response: requests.Response, _: str) -> str:
next_link = response.links.get('next')
return None if not next_link else next_link['url']
class MyClient(apiclient.APIClient):
def __init__(self, auth_method: apiclient.authentication_methods.BaseAuthenticationMethod):
super().__init__(
auth_method,
apiclient.JsonResponseHandler,
request_formatter=apiclient.JsonRequestFormatter,
request_strategy=apiclient.request_strategies.UrlPaginatedRequestStrategy(header_pagination)
)
in header_pagination, response is a dict
Expected behavior
next_page function should receive the original Response object, before decoding
Versions (please complete the following information):
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.