Skip to content
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
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.{yml,yaml,toml,md}]
indent_size = 2

[Makefile]
indent_style = tab
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
groups:
runtime:
patterns: ["httpx*", "pydantic*", "tenacity", "typer", "rich"]
dev:
patterns: ["pytest*", "ruff", "mypy*", "fakeredis", "types-*"]
redis:
patterns: ["redis"]

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 3
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
PYTHONUNBUFFERED: "1"
PYTHONHASHSEED: "random"
FORCE_COLOR: "1"

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
- run: uv sync --extra dev --extra redis
- run: uv run ruff check .
- run: uv run ruff format --check .

typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
- run: uv sync --extra dev --extra redis
- run: uv run mypy --strict src/askii

test:
name: Test (${{ matrix.os }} / Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # hatch-vcs needs tags
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
- run: uv python install ${{ matrix.python-version }}
- run: uv sync --python ${{ matrix.python-version }} --extra dev --extra redis
- run: uv run pytest --cov=askii --cov-report=term-missing --cov-report=xml --cov-fail-under=90 -v
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
uses: actions/upload-artifact@v7
with:
name: coverage-xml
path: coverage.xml
if-no-files-found: ignore

build:
name: Build
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
- run: uv build
- name: Verify py.typed in wheel
run: |
python -m zipfile -l dist/*.whl | grep "askii/py.typed"
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
name: Build and publish GitHub Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # hatch-vcs needs tags
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
- run: uv build
- name: Generate release notes from CHANGELOG
id: notes
run: |
python <<'PY' > release_notes.md
import re, sys, pathlib
tag = "${{ github.ref_name }}"
version = tag.lstrip("v")
text = pathlib.Path("CHANGELOG.md").read_text()
m = re.search(rf"## \[{re.escape(version)}\][^\n]*\n(.*?)(?=\n## |\Z)", text, re.S)
sys.stdout.write(m.group(1).strip() if m else f"Release {tag}")
PY
- uses: softprops/action-gh-release@v2
with:
files: dist/*
body_path: release_notes.md
generate_release_notes: true
43 changes: 43 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Distribution / packaging
build/
dist/
*.egg-info/
*.egg

# Generated by hatch-vcs
src/askii/_version.py

# Virtual envs
.venv/
venv/
env/

# uv
.uv-cache/

# Testing
.pytest_cache/
.coverage
.coverage.*
htmlcov/
coverage.xml

# Type checkers
.mypy_cache/
.pyright_cache/
.ruff_cache/

# OS
.DS_Store
Thumbs.db

# Editors
.idea/
.vscode/
*.swp
*.swo
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.6.0
hooks:
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: detect-private-key
- id: mixed-line-ending
args: ["--fix=lf"]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7
hooks:
- id: ruff
args: ["--fix"]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.0
hooks:
- id: mypy
additional_dependencies:
- "pydantic>=2.6"
- "httpx>=0.27"
- "tenacity>=8.2"
- "typer>=0.12"
- "rich>=13.7"
- "types-redis"
args: ["--strict"]
files: ^src/askii/
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

_No changes yet._

## [0.1.0] - 2026-05-21

### Added

- Initial release scaffolding for the Askii Python client.
- `Askii` (sync) and `AsyncAskii` (async) clients with shared transport.
- Resource-oriented API: `client.keys.*`, `client.models.*`, plus low-level `client.request()` escape hatch.
- Pydantic v2 request/response models; `SecretStr`-wrapped provisioned `api_key`.
- Typed exception hierarchy with `FieldError` mapping for 422 responses.
- Pluggable `Cache` Protocol with `InMemoryCache` and optional `RedisCache` (`[redis]` extra).
- tenacity-based retries with exponential backoff + jitter; honors `Retry-After` (now also accepts HTTP-date form).
- Per-call `idempotent` flag on `client.request()` / transport. Mutating resource methods (`keys.provision`, `keys.revoke`, `keys.update_model`) opt out of the retry policy so a transient 5xx cannot double-apply a mutation.
- Lifecycle hooks (`on_request`, `on_response`, `on_retry`, `on_cache_hit`, `on_cache_miss`, `on_error`).
- Opt-in JSON logging with secret redaction and correlation-ID propagation.
- Bundled `askii` CLI (typer + rich) covering all Platform Key Management endpoints.
- CI matrix now covers Python 3.10 through 3.14.
- `ASKII_VERIFY` and `ASKII_CA_BUNDLE` environment variables wire TLS verification into `AskiiConfig.from_env`.

### Changed

- Centralize Platform Key Management URL paths in `askii._endpoints` so upstream renames or version-prefix changes are a one-line edit.

### Fixed

- `mpass_token` injection now wins over any caller-supplied `mpass_token` in `body=…` (previously, a caller's key could shadow the resolver's token).
- `Retry-After` headers in HTTP-date form (RFC 7231 §7.1.3) are now parsed correctly; previously they fell back to exponential backoff and ignored the server's wait hint.

[Unreleased]: https://github.com/Pressingly/askii-python/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/Pressingly/askii-python/releases/tag/v0.1.0
Loading
Loading