Giter Club home page Giter Club logo

api-client's People

Contributors

bduzik avatar dp-rufus avatar emjironal avatar mikewooster avatar mindflayer avatar mom1 avatar peterschutt avatar ysaxon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

api-client's Issues

Need MORE Content-type class IN request_formatter.py

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

Allow defining a client-level base URL that is prepended to endpoint URLs in requests

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?

Package name is conflicting with google_api_python_client

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:

  1. Install api-client and google-api-python-client
  2. import anything from api_client
  3. See errors

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):

  • Python version: 3.10
  • API Client version: 1.3.1
  • Dependency versions:

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!

Extended Example's import models lost

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,
)

Define endpoints, using the provided decorator.

@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"],
}

Extend the client for your API integration.

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)

Initialize the client with the correct authentication method,

response handler and request formatter.

client = JSONPlaceholderClient(
authentication_method=HeaderAuthentication(token="<secret_value>"),
response_handler=JsonResponseHandler,
request_formatter=JsonRequestFormatter,
)

Consider adding py.typed?

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.

add more help to readme

more documentation required around using get, post, etc.
also need more documentation around initial client setup - with links to more info sections.

Don't swallow errors

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

except Exception as error:

Tenacity 7.0 breaks test suite

Tenacity has removed tenacity.compat.make_retry_state which features heavily in test_retrying.py.

from tenacity.compat import make_retry_state

To Reproduce

  1. Create new virtualenv and activate.
  2. python -m pip install .[test]
  3. 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.

Async support

It is necessary to support asynchronous requests to use in async code.

print() call

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.

print(">>>", request_method)

Edit: removed or converted to logging.

post() does not support arrays

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:

  1. Call 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.

Testing response with unittest mock

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!

`tox` command not running unittests

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.

Async Client

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:)

Extend APIClient with Additional Constructor Attributes

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.

OAuth handler

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

Extend HeaderAuthentication

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.

api-client (1.2.0) LOST module [dataclasses] Not backward compatible

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):

  • Python version: [3.6.8]
  • API Client version: [1.2.0]

request_strategy parameter for APIClient constructor

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!

Could add the debug option when request the API url

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

Get Token

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.

paginator has no access to the original response when using JsonResponseHandler

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):

  • Python version: 3.9.2
  • API Client version: 1.3.1
  • Dependency versions: requests==2.21.7, tenacity==8.0.1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.