Skip to content

Commit

Permalink
Modernize (#52)
Browse files Browse the repository at this point in the history
* modernising

* linting, fix mypy, fix ci

* pre-commit

* remove uv from CI

* fix deps and coverage

* fix test deps

* drop 3.9 and 3.13

* drop 3.9 from ci, use correct timezone
  • Loading branch information
samuelcolvin authored Aug 16, 2024
1 parent 332855f commit 574c0f8
Show file tree
Hide file tree
Showing 27 changed files with 568 additions and 249 deletions.
85 changes: 49 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,32 @@ on:
pull_request: {}

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.12'

- run: pip install -r requirements/linting.txt
- run: pip install -r requirements/pyproject.txt

- uses: pre-commit/action@v3.0.0
with:
extra_args: --all-files
env:
SKIP: no-commit-to-branch

test:
name: test py${{ matrix.python-version }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu, windows, macos]
python-version: ['3.8', '3.9', '3.10', '3.11']
os: [ubuntu, macos]
python-version: ['3.10', '3.11', '3.12']

runs-on: ${{ matrix.os }}-latest

Expand All @@ -24,15 +43,16 @@ jobs:
OS: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- run: pip install -U wheel
- run: pip install -r tests/requirements.txt
- run: pip install -r requirements/tests.txt
- run: pip install -r requirements/pyproject.txt
- run: pip install .
- run: pip freeze

Expand All @@ -49,53 +69,46 @@ jobs:
file: ./coverage.xml
env_vars: PYTHON,OS

lint:
check: # This job does nothing and is only used for the branch protection
if: always()
needs: [lint, test]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v4
with:
python-version: '3.9'

- run: pip install -r tests/requirements-linting.txt
- run: pip install .

- run: make lint
- run: make mypy
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
id: all-green
with:
jobs: ${{ toJSON(needs) }}

deploy:
needs:
- test
- lint
release:
needs: [check]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
environment: release

permissions:
id-token: write

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.12'

- name: install
run: pip install -U twine build packaging
run: pip install -U build

- name: check version
id: check-version
run: python <(curl -Ls https://gist.githubusercontent.com/samuelcolvin/4e1ad439c5489e8d6478cdee3eb952ef/raw/check_version.py)
env:
VERSION_PATH: 'aioaws/version.py'
uses: samuelcolvin/check-python-version@v3.2
with:
version_file_path: 'aioaws/version.py'

- name: build
run: python -m build

- run: twine check dist/*

- name: upload to pypi
run: twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.pypi_token }}
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
*.py[cod]
.idea/
env/
/env/
/env*/
.coverage
.cache/
Expand Down
32 changes: 32 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: no-commit-to-branch
- id: check-yaml
args: ['--unsafe']
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files

- repo: local
hooks:
- id: format
name: format
entry: make format
types: [python]
language: system
pass_filenames: false
- id: lint
name: lint
entry: make lint
types: [python]
language: system
pass_filenames: false
- id: mypy
name: mypy
entry: make mypy
types: [python]
language: system
pass_filenames: false
36 changes: 23 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
.DEFAULT_GOAL := all
isort = isort aioaws tests
black = black aioaws tests
ruff = ruff aioaws tests

.PHONY: install
install:
python -m pip install -U setuptools pip
pip install -U -r requirements.txt
pip install -U -r tests/requirements-linting.txt
pip install -e .
pip install -U pip pre-commit pip-tools
pip install -r requirements/all.txt
pre-commit install

.PHONY: refresh-lockfiles
refresh-lockfiles:
@echo "Replacing requirements/*.txt files using pip-compile"
find requirements/ -name '*.txt' ! -name 'all.txt' -type f -delete
make update-lockfiles

.PHONY: update-lockfiles
update-lockfiles:
@echo "Updating requirements/*.txt files using pip-compile"
pip-compile --strip-extras -q -o requirements/linting.txt requirements/linting.in
pip-compile --strip-extras -q -o requirements/tests.txt -c requirements/linting.txt requirements/tests.in
pip-compile --strip-extras -q -o requirements/pyproject.txt \
-c requirements/linting.txt -c requirements/tests.txt \
pyproject.toml
pip install --dry-run -r requirements/all.txt

.PHONY: format
format:
$(isort)
$(black)
$(ruff) --fix --exit-zero
ruff check --fix-only aioaws tests
ruff format aioaws tests

.PHONY: lint
lint:
$(ruff)
$(isort) --check-only --df
$(black) --check --diff
ruff check aioaws tests
ruff format --check aioaws tests

.PHONY: mypy
mypy:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async def ses_webhook(request: Request):
except SnsWebhookError as e:
debug(message=e.message, details=e.details, headers=e.headers)
raise ...

debug(webhook_info)
...
```
Expand Down
17 changes: 12 additions & 5 deletions aioaws/_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
from collections.abc import Coroutine, Iterable
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Coroutine, Iterable, List, Optional
from typing import TYPE_CHECKING, Any

from httpx import Response

Expand Down Expand Up @@ -34,7 +35,7 @@ def to_unix_s(dt: datetime) -> int:


def utcnow() -> datetime:
return datetime.utcnow().replace(tzinfo=timezone.utc)
return datetime.now(tz=timezone.utc)


class ManyTasks:
Expand All @@ -45,9 +46,9 @@ class ManyTasks:
__slots__ = '_tasks'

def __init__(self) -> None:
self._tasks: List[asyncio.Task[Any]] = []
self._tasks: list[asyncio.Task[Any]] = []

def add(self, coroutine: Coroutine[Any, Any, List[str]], *, name: Optional[str] = None) -> None:
def add(self, coroutine: Coroutine[Any, Any, list[str]], *, name: str | None = None) -> None:
task = asyncio.create_task(coroutine, name=name)
self._tasks.append(task)

Expand All @@ -67,7 +68,13 @@ def pretty_xml(response_xml: bytes) -> str:


def pretty_response(r: Response) -> None: # pragma: no cover
from devtools import debug
try:
from devtools import debug
except ImportError:
from pprint import pprint

def debug(**kwargs: Any) -> None:
pprint(kwargs)

debug(
status=r.status_code,
Expand Down
37 changes: 19 additions & 18 deletions aioaws/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import hmac
import logging
from binascii import hexlify
from collections.abc import Generator
from datetime import datetime
from functools import reduce
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Literal, Optional, Tuple
from typing import TYPE_CHECKING, Any, Literal
from urllib.parse import quote as url_quote

from httpx import URL, AsyncClient, Auth, Request, Response
Expand Down Expand Up @@ -61,17 +62,17 @@ def __init__(self, client: AsyncClient, config: 'BaseConfigProtocol', service: L
def endpoint(self) -> str:
return f'{self.schema}://{self.host}'

async def get(self, path: str = '', *, params: Optional[Dict[str, Any]] = None) -> Response:
async def get(self, path: str = '', *, params: dict[str, Any] | None = None) -> Response:
return await self.request('GET', path=path, params=params)

async def raw_post(
self,
url: str,
*,
expected_status: int,
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, str]] = None,
files: Optional[Dict[str, bytes]] = None,
params: dict[str, Any] | None = None,
data: dict[str, str] | None = None,
files: dict[str, bytes] | None = None,
) -> Response:
r = await self.client.post(url, params=params, data=data, files=files)
if r.status_code == expected_status:
Expand All @@ -85,9 +86,9 @@ async def post(
self,
path: str = '',
*,
params: Optional[Dict[str, Any]] = None,
data: Optional[bytes] = None,
content_type: Optional[str] = None,
params: dict[str, Any] | None = None,
data: bytes | None = None,
content_type: str | None = None,
) -> Response:
return await self.request('POST', path=path, params=params, data=data, content_type=content_type)

Expand All @@ -96,9 +97,9 @@ async def request(
method: Literal['GET', 'POST'],
*,
path: str,
params: Optional[Dict[str, Any]],
data: Optional[bytes] = None,
content_type: Optional[str] = None,
params: dict[str, Any] | None,
data: bytes | None = None,
content_type: str | None = None,
) -> Response:
url = URL(f'{self.endpoint}{path}', params=[(k, v) for k, v in sorted((params or {}).items())])
r = await self.client.request(
Expand Down Expand Up @@ -129,14 +130,14 @@ def add_signed_download_params(self, method: Literal['GET', 'POST'], url: URL, e
_, signature = self._auth.aws4_signature(now, method, url, {'host': self.host}, 'UNSIGNED-PAYLOAD')
return url.copy_add_param('X-Amz-Signature', signature)

def upload_extra_conditions(self, dt: datetime) -> List[Dict[str, str]]:
def upload_extra_conditions(self, dt: datetime) -> list[dict[str, str]]:
return [
{'x-amz-credential': self._auth.aws4_credential(dt)},
{'x-amz-algorithm': _AUTH_ALGORITHM},
{'x-amz-date': _aws4_x_amz_date(dt)},
]

def signed_upload_fields(self, dt: datetime, string_to_sign: str) -> Dict[str, str]:
def signed_upload_fields(self, dt: datetime, string_to_sign: str) -> dict[str, str]:
return {
'X-Amz-Algorithm': _AUTH_ALGORITHM,
'X-Amz-Credential': self._auth.aws4_credential(dt),
Expand All @@ -163,9 +164,9 @@ def auth_headers(
method: Literal['GET', 'POST'],
url: URL,
*,
data: Optional[bytes] = None,
content_type: Optional[str] = None,
) -> Dict[str, str]:
data: bytes | None = None,
content_type: str | None = None,
) -> dict[str, str]:
now = utcnow()
data = data or b''
content_type = content_type or _CONTENT_TYPE
Expand All @@ -188,8 +189,8 @@ def auth_headers(
return headers

def aws4_signature(
self, dt: datetime, method: Literal['GET', 'POST'], url: URL, headers: Dict[str, str], payload_hash: str
) -> Tuple[str, str]:
self, dt: datetime, method: Literal['GET', 'POST'], url: URL, headers: dict[str, str], payload_hash: str
) -> tuple[str, str]:
header_keys = sorted(headers)
signed_headers = ';'.join(header_keys)
canonical_request_parts = (
Expand Down
Loading

0 comments on commit 574c0f8

Please sign in to comment.