Skip to content

Commit 1168e2f

Browse files
committed
[v1.0.0] Refactor API wrappers to use centralized helper methods
Replace manual additional_properties handling with BaseAPI helpers throughout all API modules. Fix parameter passing, response checks, and async/sync consistency issues. Add project infrastructure: - MIT license and contributing guidelines - GitHub Actions workflow for PyPI publishing - Python packaging configuration with uv - CLI tool initialization module API fixes: - Use _sync_ap/_async_ap for additional_properties extraction - Use _sync_ap_list/_async_ap_list for list comprehensions - Use _sync, _sync_detailed, and _sync_detailed_model properly - Fix operations without body params to use correct helpers - Correct missing parameters and type signatures - Standardize response unwrapping and error handling Update documentation and configure project metadata for distribution. Signed-off-by: Phillip Sitbon <[email protected]>
1 parent 600d91f commit 1168e2f

37 files changed

+3251
-1737
lines changed

.env.example

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11

22
# - For Docker Compose, you can set the project name to avoid conflicts with other projects.
3-
# export COMPOSE_PROJECT_NAME=ackc
3+
# export COMPOSE_PROJECT_NAME=auth
44

55
# - Environment variable prefix for the variables below.
66
# Non-prefixed variables will still be used if prefixed values are not set.
77
# export KEYCLOAK_ENV_PREFIX=AUTH_
88

99
# - URL to your Keycloak server.
10-
# export KEYCLOAK_URL=https://id.acie.dev
10+
# export KEYCLOAK_URL=https://id.example.com
1111
# - Management interface URL, usually only Docker/VPC-local.
1212
# export KEYCLOAK_MANAGEMENT_URL=http://localhost:9000
1313

1414
# - Keycloak realm for your application.
15-
# export KEYCLOAK_REALM=acie
15+
# export KEYCLOAK_REALM=example
1616
# - Keycloak authentication realm for the client.
17-
# export KEYCLOAK_AUTH_REALM=acie
17+
# export KEYCLOAK_AUTH_REALM=example
1818

1919
# - Default Keycloak client ID and secret for authentication.
2020
# export KEYCLOAK_CLIENT_ID=ackc-admin

.github/workflows/publish.yml

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
check-version:
9+
name: Check Version Change
10+
runs-on: ubuntu-latest
11+
environment: Publish
12+
outputs:
13+
should_publish: ${{ steps.version_check.outputs.changed }}
14+
version: ${{ steps.version_check.outputs.version }}
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
token: ${{ secrets.PAT_TOKEN }}
22+
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@v6
25+
with:
26+
enable-cache: true
27+
28+
- name: Check if version changed
29+
id: version_check
30+
run: |
31+
# Get current version from pyproject.toml
32+
CURRENT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
33+
echo "version=${CURRENT_VERSION}" >> $GITHUB_OUTPUT
34+
35+
# Get last published version from latest-publish tag
36+
if git show-ref --tags --quiet --verify refs/tags/latest-publish; then
37+
# Checkout the last published commit
38+
git checkout latest-publish
39+
LAST_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
40+
git checkout -
41+
else
42+
LAST_VERSION=""
43+
fi
44+
45+
echo "Current version: ${CURRENT_VERSION}"
46+
echo "Last published version: ${LAST_VERSION:-none}"
47+
48+
if [ "${CURRENT_VERSION}" != "${LAST_VERSION}" ]; then
49+
echo "Version changed, will publish"
50+
echo "changed=true" >> $GITHUB_OUTPUT
51+
else
52+
echo "Version unchanged, skipping publish"
53+
echo "changed=false" >> $GITHUB_OUTPUT
54+
fi
55+
56+
publish:
57+
name: Publish PyPI Package and GitHub Release
58+
needs: check-version
59+
if: ${{ needs.check-version.outputs.should_publish == 'true' }}
60+
runs-on: ubuntu-latest
61+
environment: Publish
62+
permissions:
63+
contents: write
64+
id-token: write
65+
66+
steps:
67+
- name: Checkout code
68+
uses: actions/checkout@v4
69+
with:
70+
fetch-depth: 0 # Need full history for commit count
71+
token: ${{ secrets.PAT_TOKEN }}
72+
73+
- name: Install uv
74+
uses: astral-sh/setup-uv@v6
75+
with:
76+
enable-cache: true
77+
78+
- name: Set up Python
79+
run: uv python install
80+
81+
- name: Install dependencies
82+
run: |
83+
uv sync --all-groups --locked
84+
85+
- name: Import GPG key
86+
uses: crazy-max/ghaction-import-gpg@v6
87+
with:
88+
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
89+
passphrase: ${{ secrets.GPG_PASSPHRASE }}
90+
git_user_signingkey: true
91+
git_commit_gpgsign: true
92+
git_tag_gpgsign: true
93+
94+
- name: Configure Git
95+
run: |
96+
git config user.name "${{ vars.SIGNED_COMMIT_USER }}"
97+
git config user.email "${{ vars.SIGNED_COMMIT_EMAIL }}"
98+
git config commit.gpgsign true
99+
git config tag.gpgsign true
100+
101+
- name: Build and tag release
102+
run: |
103+
# Get current version (3-part)
104+
V=${{ needs.check-version.outputs.version }}
105+
echo "VERSION=${V}" >> $GITHUB_ENV
106+
107+
# Build the package
108+
UV_FROZEN=true uv build
109+
110+
# Create a 3-part version tag
111+
TAG="v${V}"
112+
echo "TAG=${TAG}" >> $GITHUB_ENV
113+
TAG_MESSAGE="[Automatic] Release Version ${V} from $(git rev-parse --short HEAD)"
114+
git tag -s -m "${TAG_MESSAGE}" "${TAG}"
115+
116+
# Create or update 2-digit version tag (simple, unsigned)
117+
MAJOR_MINOR=$(echo ${V} | cut -d. -f1-2)
118+
TAG_2DIGIT="v${MAJOR_MINOR}"
119+
# Delete existing 2-digit tag if it exists
120+
git tag -d "${TAG_2DIGIT}" 2>/dev/null || true
121+
git push origin :refs/tags/"${TAG_2DIGIT}" 2>/dev/null || true
122+
# Create new simple unsigned tag
123+
git tag --no-sign "${TAG_2DIGIT}"
124+
125+
# Update latest-publish tag after getting changelog
126+
LATEST_TAG="latest-publish"
127+
128+
# Get changelog since last release with rich formatting
129+
echo "CHANGELOG<<EOFEOF" >> $GITHUB_ENV
130+
git log ${LATEST_TAG}..${{ github.sha }} --pretty=format:'### [%s](https://github.com/${{ github.repository }}/commit/%H)%nDate: %ad%n%n%b%n' | sed '/^Signed-off-by:/d' >> $GITHUB_ENV
131+
echo "EOFEOF" >> $GITHUB_ENV
132+
133+
# Delete remote latest-publish tag FIRST (before creating new one)
134+
git push origin :refs/tags/${LATEST_TAG} || true
135+
136+
# Now create the new latest-publish tag locally
137+
git tag -d ${LATEST_TAG} || true
138+
git tag --no-sign ${LATEST_TAG}
139+
140+
# Push all tags to remote
141+
git push origin --tags
142+
143+
- name: Create GitHub Release
144+
uses: softprops/action-gh-release@v2
145+
with:
146+
tag_name: ${{ env.TAG }}
147+
name: ACKC Release v${{ env.VERSION }}
148+
body: |
149+
## Changes
150+
${{ env.CHANGELOG }}
151+
152+
## Installation
153+
```bash
154+
uv add ackc==${{ env.VERSION }}
155+
```
156+
files: |
157+
dist/*.tar.gz
158+
dist/*.whl
159+
160+
- name: Publish to PyPI
161+
if: success()
162+
env:
163+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
164+
run: |
165+
uv publish --token $PYPI_TOKEN

.github/workflows/readme.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# GitHub Actions Workflows for ACKC
2+
3+
This directory contains GitHub Actions workflows for automated publishing of the ACKC package to PyPI.
4+
5+
## Workflows
6+
7+
### Publish to PyPI (`publish.yml`)
8+
- **Trigger**: On push to main branch
9+
- **Purpose**: Automatically publish new versions to PyPI when version changes
10+
- **Actions**:
11+
1. Check if version in pyproject.toml has changed since last publish
12+
2. If changed, build the package
13+
3. Create GPG-signed tag (vX.Y.Z)
14+
4. Create simplified tag for major.minor (vX.Y)
15+
5. Push tags to repository
16+
6. Create GitHub release with changelog
17+
7. Publish to PyPI
18+
8. Update latest-publish tag for future comparisons
19+
20+
## Required Secrets
21+
22+
The following secrets must be configured in the GitHub repository settings:
23+
24+
- `PAT_TOKEN`: Personal Access Token with read/write permissions on Content
25+
- `GPG_PRIVATE_KEY`: The GPG private key for signing commits and tags
26+
- `GPG_PASSPHRASE`: The passphrase for the GPG key
27+
- `PYPI_TOKEN`: The API token for publishing to PyPI
28+
29+
## Required Environment Variables
30+
31+
The following environment variables should be set in the `Publish` environment:
32+
33+
- `GPG_PUBLIC_KEY`: The GPG public key (informational)
34+
- `PYPI_TOKEN_NAME`: The name of the PyPI token (informational)
35+
- `SIGNED_COMMIT_USER`: The name for git commits
36+
- `SIGNED_COMMIT_EMAIL`: The email for git commits
37+
38+
## Version Numbering
39+
40+
The version number follows the pattern `X.Y.Z` where:
41+
- X = Major version (breaking changes)
42+
- Y = Minor version (new features)
43+
- Z = Patch version (bug fixes)
44+
45+
The workflow only publishes when the version in pyproject.toml is manually changed.
46+
47+
## Environment Configuration
48+
49+
The publish environment requires:
50+
- `SIGNED_COMMIT_USER`: Git username for commits
51+
- `SIGNED_COMMIT_EMAIL`: Git user email for commits
52+
53+
## Usage
54+
55+
**Publishing to PyPI**:
56+
1. Update version in pyproject.toml
57+
2. Commit and push to main branch
58+
3. Workflow will automatically detect version change and publish
59+
60+
## Branch Strategy
61+
62+
- **main**: Production branch - triggers PyPI releases
63+
64+
## Troubleshooting
65+
66+
- If git operations fail, ensure the PAT_TOKEN has sufficient permissions
67+
- If GPG signing fails, ensure the GPG key is properly imported and the secrets are correct
68+
- If PyPI publishing fails, check that the PYPI_TOKEN has sufficient permissions

ackc/__init__.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
"""ACIE Auth Client - High-performance Keycloak client using niquests.
2-
3-
This package provides a modern, async-first Python client for Keycloak with:
4-
- Auto-generated API from OpenAPI spec
5-
- High-performance niquests backend (HTTP/2, multiplexing)
6-
- Type safety with Pydantic models
7-
- Async and sync interfaces
8-
9-
Basic usage:
10-
from acie.auth.client import KeycloakClient
11-
12-
# Client will auto-authenticate using KEYCLOAK_* env vars
13-
client = KeycloakClient()
14-
15-
# Use the generated API directly
16-
from acie.auth.client.api.users import get_admin_realms_realm_users
17-
users = get_admin_realms_realm_users.sync(realm="master", client=client)
1+
"""ACKC - Keycloak API client using niquests.
182
"""
193
from importlib.metadata import version
204

@@ -29,6 +13,9 @@
2913
TokenExpiredError,
3014
InvalidTokenError,
3115
UserNotFoundError,
16+
RealmNotFoundError,
17+
ClientNotFoundError,
18+
APIError,
3219
)
3320

3421
__version__ = version("ackc")
@@ -51,4 +38,7 @@
5138
"TokenExpiredError",
5239
"InvalidTokenError",
5340
"UserNotFoundError",
54-
)
41+
"RealmNotFoundError",
42+
"ClientNotFoundError",
43+
"APIError",
44+
)

ackc/api/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@
3030

3131
__all__ = (
3232
"AuthError", "APIError", "AuthenticatedClient", "Client", "BaseAPI", "BaseClientManager",
33-
"UsersAPI", "UsersClientMixin", "UserRepresentation",
33+
"UsersAPI", "UsersClientMixin", "UserRepresentation", "CredentialRepresentation", "UserConsentRepresentation", "FederatedIdentityRepresentation",
3434
"RealmsAPI", "RealmsClientMixin", "RealmRepresentation",
35-
"ClientsAPI", "ClientsClientMixin", "ClientRepresentation",
35+
"ClientsAPI", "ClientsClientMixin", "ClientRepresentation", "ManagementPermissionReference",
3636
"RolesAPI", "RolesClientMixin", "RoleRepresentation",
3737
"GroupsAPI", "GroupsClientMixin", "GroupRepresentation",
3838
"OrganizationsAPI", "OrganizationsClientMixin", "OrganizationRepresentation",
3939
"IdentityProvidersAPI", "IdentityProvidersClientMixin", "IdentityProviderRepresentation",
40-
"ClientScopesAPI", "ClientScopesClientMixin", "ClientScopeRepresentation",
41-
"ComponentsAPI", "ComponentsClientMixin", "ComponentRepresentation",
40+
"ClientScopesAPI", "ClientScopesClientMixin", "ClientScopeRepresentation", "GlobalRequestResult",
41+
"ComponentsAPI", "ComponentsClientMixin", "ComponentRepresentation", "ComponentTypeRepresentation",
4242
"SessionsAPI", "SessionsClientMixin", "UserSessionRepresentation",
4343
"EventsAPI", "EventsClientMixin", "RealmEventsConfigRepresentation", "EventRepresentation", "AdminEventRepresentation",
4444
"AuthenticationAPI", "AuthenticationClientMixin", "AuthenticationFlowRepresentation", "AuthenticationExecutionInfoRepresentation", "AuthenticatorConfigRepresentation", "RequiredActionProviderRepresentation",
45-
"AuthorizationAPI", "AuthorizationClientMixin", "ResourceServerRepresentation", "ResourceRepresentation", "ScopeRepresentation", "AbstractPolicyRepresentation", "PolicyProviderRepresentation", "PolicyEvaluationResponse", "EvaluationResultRepresentation",
45+
"AuthorizationAPI", "AuthorizationClientMixin", "ResourceServerRepresentation", "ResourceRepresentation", "ScopeRepresentation", "AbstractPolicyRepresentation", "PolicyProviderRepresentation", "PolicyEvaluationResponse", "EvaluationResultRepresentation", "PolicyRepresentation",
4646
"ProtocolMappersAPI", "ProtocolMappersClientMixin", "ProtocolMapperRepresentation",
4747
"KeysAPI", "KeysClientMixin", "KeysMetadataRepresentation",
4848
"ScopeMappingsAPI", "ScopeMappingsClientMixin",
4949
"ClientRoleMappingsAPI", "ClientRoleMappingsClientMixin",
5050
"RoleMapperAPI", "RoleMapperClientMixin",
5151
"RolesByIdAPI", "RolesByIdClientMixin",
5252
"AttackDetectionAPI", "AttackDetectionClientMixin",
53-
"ClientInitialAccessAPI", "ClientInitialAccessClientMixin",
54-
"ClientAttributeCertificateAPI", "ClientAttributeCertificateClientMixin",
53+
"ClientInitialAccessAPI", "ClientInitialAccessClientMixin", "ClientInitialAccessPresentation", "ClientInitialAccessCreatePresentation",
54+
"ClientAttributeCertificateAPI", "ClientAttributeCertificateClientMixin", "CertificateRepresentation", "KeyStoreConfig",
5555
"ClientRegistrationPolicyAPI", "ClientRegistrationPolicyClientMixin",
5656
"KeycloakClientMixin",
5757
)

0 commit comments

Comments
 (0)