Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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/auto_merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
auto-merge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE }}
8 changes: 4 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up Python 3.8
uses: actions/setup-python@v2.2.2
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Install mkdocs
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: release

on: push
on:
release:
types: [published]

jobs:
publish-pypi:
Expand All @@ -16,8 +18,7 @@ jobs:
- name: Generating distribution archives
run: python setup.py sdist bdist_wheel
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
23 changes: 12 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: test

on: [push, pull_request]
on: push

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Lint
Expand All @@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10']
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -35,18 +35,19 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Generate coverage report
run: pytest --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1.5.0
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
SHELL := bash
PATH := ./venv/bin:${PATH}
PYTHON = python3.9
PYTHON = python3.13
PROJECT = facturapi
isort = isort $(PROJECT) tests setup.py examples
black = black -S -l 79 --target-version py38 $(PROJECT) tests setup.py examples
black = black -S -l 79 --target-version py313 $(PROJECT) tests setup.py examples


all: test
Expand Down
48 changes: 24 additions & 24 deletions facturapi/http/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
from typing import Any, Dict, MutableMapping, Optional, Union
from typing import Any, MutableMapping
from urllib.parse import urljoin

import requests
from requests import Response
import httpx
from httpx import Response

from ..types.exc import FacturapiResponseException
from ..version import CLIENT_VERSION
Expand All @@ -21,18 +21,18 @@ class Client:

Attributes:
host (str): Base URL to perform requests.
session (requests.Session): The requests session used
client (httpx.Client): The httpx client used
to perform requests.
api_key (str): API KEY for Facturapi

"""

host: str = API_HOST
session: requests.Session
client: httpx.Client

def __init__(self):
self.session = requests.Session()
self.session.headers.update(
def __init__(self) -> None:
self.client = httpx.Client()
self.client.headers.update(
{
'User-Agent': f'facturapi-python/{CLIENT_VERSION}',
'Content-Type': 'application/json',
Expand All @@ -41,9 +41,9 @@ def __init__(self):

# Auth
self.api_key = os.getenv('FACTURAPI_KEY', '')
self.session.auth = (self.api_key, '')
self.client.auth = httpx.BasicAuth(self.api_key, '')

def configure(self, api_key: str):
def configure(self, api_key: str) -> None:
"""Configure the http client.

Import the client and configure it passing the `API_KEY`
Expand All @@ -54,36 +54,36 @@ def configure(self, api_key: str):

"""
self.api_key = api_key
self.session.auth = (self.api_key, '')
self.client.auth = httpx.BasicAuth(self.api_key, '')

def get(
self,
endpoint: str,
params: Union[None, bytes, MutableMapping[str, str]] = None,
) -> Dict[str, Any]:
params: bytes | MutableMapping[str, str] | None = None,
) -> dict[str, Any]:
"""Performs GET request to Facturapi."""
return self.request('get', endpoint, params=params)

def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
def post(self, endpoint: str, data: dict[str, Any]) -> dict[str, Any]:
"""Performs POST request to Facturapi."""
return self.request('post', endpoint, data=data)

def put(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
def put(self, endpoint: str, data: dict[str, Any]) -> dict[str, Any]:
"""Performs PUT request to Facturapi."""
return self.request('put', endpoint, data=data)

def delete(self, endpoint: str) -> Dict[str, Any]:
def delete(self, endpoint: str) -> dict[str, Any]:
"""Performs DELETE request to Facturapi."""
return self.request('delete', endpoint)

def request(
self,
method: str,
endpoint: str,
params: Union[None, bytes, MutableMapping[str, str]] = None,
data: Optional[Dict[str, Union[int, str]]] = None,
params: bytes | MutableMapping[str, str] | None = None,
data: dict[str, int | str] | None = None,
**kwargs,
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""Performs a request to Facturapi.

Given a `method` and `endpoint`, perform a request to
Expand All @@ -97,14 +97,14 @@ def request(
**kwargs: Arbitrary keyword arguments.

Returns:
Dict[str, Any]: JSON of the request's response.
dict[str, Any]: JSON of the request's response.

Raises:
FacturapiResponseException: If response is not
successful.

"""
response = self.session.request(
response = self.client.request(
method=method,
url=('https://' + self.host + urljoin('/', endpoint)),
json=data,
Expand Down Expand Up @@ -133,7 +133,7 @@ def download_request(
successful.

"""
response = self.session.request(
response = self.client.request(
method='GET',
url=('https://' + self.host + urljoin('/', endpoint)),
**kwargs,
Expand All @@ -142,8 +142,8 @@ def download_request(
return response.content

@staticmethod
def _check_response(response: Response):
if not response.ok:
def _check_response(response: Response) -> None:
if not response.is_success:
raise FacturapiResponseException(
json=response.json(),
status_code=response.status_code,
Expand Down
14 changes: 7 additions & 7 deletions facturapi/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

from dataclasses import asdict, fields
from typing import Any, ClassVar, Dict, Generator, List, Optional
from typing import Any, ClassVar, Generator
from urllib.parse import urlencode

from pydantic.dataclasses import dataclass
Expand All @@ -31,7 +31,7 @@ class corresponds to.
"""

_resource: ClassVar[str]
_relations: ClassVar[List[str]] = []
_relations: ClassVar[list[str]] = []

id: str

Expand All @@ -40,12 +40,12 @@ def __init__(self, **_): # pragma: no cover
...

@classmethod
def _from_dict(cls, obj_dict: Dict[str, Any]) -> 'Resource':
def _from_dict(cls, obj_dict: dict[str, Any]) -> 'Resource':
cls._filter_excess_fields(obj_dict)
return cls(**obj_dict)

@classmethod
def _filter_excess_fields(cls, obj_dict: Dict[str, Any]) -> None:
def _filter_excess_fields(cls, obj_dict: dict[str, Any]) -> None:
"""
dataclasses don't allow __init__ to be called with excess fields.
This method allows the API to add fields in the response body without
Expand All @@ -62,7 +62,7 @@ def _filter_excess_fields(cls, obj_dict: Dict[str, Any]) -> None:
obj_dict[f'{f}_info'] = obj_dict[f]
del obj_dict[f]

def to_dict(self) -> Dict:
def to_dict(self) -> dict:
return asdict(self, dict_factory=SanitizedDict)


Expand Down Expand Up @@ -90,7 +90,7 @@ def retrieve(cls, id: str) -> Resource:
response = client.get(f'/{cls._resource}/{id}')
return cls._from_dict(response)

def refresh(self):
def refresh(self) -> None:
"""Refresh a resource

Refresh resource's data to be sure its the latest. It
Expand Down Expand Up @@ -256,7 +256,7 @@ def one(cls, **query_params) -> Resource:
return cls._from_dict(items[0])

@classmethod
def first(cls, **query_params) -> Optional[Resource]:
def first(cls, **query_params) -> Resource | None:
"""Retrieve the first resource found given a query or none.

Args:
Expand Down
24 changes: 12 additions & 12 deletions facturapi/resources/customers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

import datetime as dt
from typing import ClassVar, Optional, cast
from typing import ClassVar, cast

from pydantic import BaseModel
from pydantic.dataclasses import dataclass
Expand Down Expand Up @@ -34,7 +34,7 @@ class CustomerRequest(BaseModel):
tax_id: str
tax_system: TaxSystemType
email: str
phone: Optional[str]
phone: str | None = None
address: CustomerAddress


Expand All @@ -53,12 +53,12 @@ class CustomerUpdateRequest(BaseModel):

"""

legal_name: Optional[str]
tax_id: Optional[str]
tax_system: Optional[TaxSystemType]
email: Optional[str]
phone: Optional[str]
address: Optional[CustomerAddress]
legal_name: str | None = None
tax_id: str | None = None
tax_system: TaxSystemType | None = None
email: str | None = None
phone: str | None = None
address: CustomerAddress | None = None


@dataclass
Expand Down Expand Up @@ -89,8 +89,8 @@ class Customer(Creatable, Queryable, Retrievable, Updatable):
tax_id: str
email: str
address: CustomerAddress
tax_system: Optional[TaxSystemType] = None
phone: Optional[str] = None
tax_system: TaxSystemType | None = None
phone: str | None = None

@classmethod
def create(cls, data: CustomerRequest) -> 'Customer':
Expand All @@ -103,7 +103,7 @@ def create(cls, data: CustomerRequest) -> 'Customer':
Customer: The created customer resource.

"""
cleaned_data = data.dict(exclude_unset=True, exclude_none=True)
cleaned_data = data.model_dump(exclude_unset=True, exclude_none=True)
return cast('Customer', cls._create(**cleaned_data))

@classmethod
Expand All @@ -118,5 +118,5 @@ def update(cls, id: str, data: CustomerUpdateRequest) -> 'Customer':
Customer: The udpated customer resource.

"""
cleaned_data = data.dict(exclude_unset=True, exclude_none=True)
cleaned_data = data.model_dump(exclude_unset=True, exclude_none=True)
return cast('Customer', cls._update(id=id, **cleaned_data))
Loading
Loading