Skip to content
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

Customize LagoApiError #55

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pypi-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,42 @@ python3 -m unittest tests

The Lago documentation is available at [doc.getlago.com](https://doc.getlago.com/docs/api/intro).

## Changelog

* [#55](https://github.com/getlago/lago-python-client/pull/55) -- Error handling (`LagoApiError`)


Example, creating wallet:

```
try:
response = client.wallets().create(wallet)
except LagoApiError as error:
do_something(status=error.status_code)
```
### Available properties:
```
>>> error.status_code
422

>>> error.detail
'Unprocessable Entity'

>>> error.headers
{'X-Frame-Options': 'SAMEORIGIN', 'X-XSS-Protection': '0', 'X-Content-Type-Options': 'nosniff', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-cache', 'X-Request-Id': '613e7542-b29e-4224-bd19-a16dd1bfa62b', 'X-Runtime': '1.024304', 'Vary': 'Origin', 'Transfer-Encoding': 'chunked'}

>>> error.response
{'status': 422, 'error': 'Unprocessable Entity', 'code': 'validation_errors', 'error_details': {'customer': ['wallet_already_exists']}}

>>> error.response['error_details']['customer'][0]
'wallet_already_exists'


>>> error.url
'http://localhost:3000/api/v1/wallets'
```


## Contributing

The contribution documentation is available [here](https://github.com/getlago/lago-python-client/blob/main/CONTRIBUTING.md)
Expand Down
53 changes: 42 additions & 11 deletions lago_python_client/clients/base_client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import requests
from collections.abc import MutableMapping
import json
from http import HTTPStatus
from typing import Any
from urllib.parse import urljoin, urlencode

import orjson
from pydantic import BaseModel
import requests
from requests import Response
from typing import Dict
from urllib.parse import urljoin, urlencode

from lago_python_client.version import LAGO_VERSION


Expand All @@ -15,7 +19,7 @@ def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.api_key = api_key

def find(self, resource_id: str, params: Dict = None):
def find(self, resource_id: str, params: dict | None = None):
api_resource = self.api_resource() + '/' + resource_id
query_url = urljoin(self.base_url, api_resource)

Expand All @@ -28,7 +32,7 @@ def find(self, resource_id: str, params: Dict = None):

return self.prepare_response(data)

def find_all(self, options: Dict = None):
def find_all(self, options: dict | None = None):
if options:
api_resource = self.api_resource() + '?' + urlencode(options)
else:
Expand Down Expand Up @@ -64,7 +68,7 @@ def create(self, input_object: BaseModel):
else:
return self.prepare_response(data.json().get(self.root_name()))

def update(self, input_object: BaseModel, identifier: str = None):
def update(self, input_object: BaseModel, identifier: str | None = None):
api_resource = self.api_resource()

if identifier is not None:
Expand All @@ -91,19 +95,28 @@ def headers(self):

return headers

def handle_response(self, response: Response):
def handle_response(self, response: Response) -> Response | None:
if response.status_code in BaseClient.RESPONSE_SUCCESS_CODES:
if response.text:
return response
else:
return None
else:
if response.text:
response_data: Any = orjson.loads(response.text)
detail: str | None = getattr(response_data, 'error', None)
else:
response_data = None
detail = None
raise LagoApiError(
"URI: %s. Status code: %s. Response: %s." % (
response.request.url, response.status_code, response.text)
status_code=response.status_code,
url=response.request.url,
response=response_data,
detail=detail,
headers=response.headers,
)

def prepare_index_response(self, data: Dict):
def prepare_index_response(self, data: dict):
collection = []

for el in data[self.api_resource()]:
Expand All @@ -118,4 +131,22 @@ def prepare_index_response(self, data: Dict):


class LagoApiError(Exception):
...
def __init__(
self,
status_code: int,
url: str | None,
response: Any,
detail: str | None = None,
headers: MutableMapping[str, str] | None = None,
) -> None:
if detail is None:
detail = HTTPStatus(status_code).phrase
self.status_code = status_code
self.url = url
self.response = response
self.detail = detail
self.headers = headers

def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name}(status_code={self.status_code!r}, detail={self.detail!r})"
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package_dir =
packages = find:
python_requires = >3.6
install_requires =
orjson
requests==2.26.0
pydantic
requests-mock