Skip to content

Commit dc17d24

Browse files
authored
feat: add linter, formatter and static type checker (#49)
* feat: add ruff linter Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: run ruff format Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: resolve linter issues Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * feat: add mypy static typing Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: correct mypy errors Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: ignore cache folders Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * refactor: simplify tasks names Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> --------- Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com>
1 parent 7fea52d commit dc17d24

35 files changed

Lines changed: 1148 additions & 529 deletions

.github/workflows/ci.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@ jobs:
5757
echo "code=${{ steps.filter.outputs.code }}" >> $GITHUB_OUTPUT
5858
fi
5959
60+
lint:
61+
name: Lint
62+
needs: [changes]
63+
if: needs.changes.outputs.code == 'true'
64+
runs-on: ubuntu-latest
65+
steps:
66+
- name: Checkout code
67+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
68+
69+
- name: Install Task
70+
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
71+
72+
- name: Lint Python SDK
73+
run: task lint
74+
6075
test:
6176
name: Test
6277
needs:
@@ -79,6 +94,7 @@ jobs:
7994
if: ${{ !cancelled() && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'failure') }}
8095
needs:
8196
- changes
97+
- lint
8298
- test
8399
- release
84100
runs-on: ubuntu-latest

.github/workflows/reusable-release-sdk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ jobs:
2929

3030
- name: Build the Python package
3131
run: |
32-
task sdk:build:python
32+
task build
3333
3434
- name: Publish the Python SDK
3535
env:
3636
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
3737
run: |
38-
task sdk:release:python
38+
task release

.github/workflows/reusable-test-sdk.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
env:
4444
CLIENT_ID: "https://github.com/${{ github.repository }}/.github/workflows/reusable-test-sdk.yaml@${{ github.ref }}"
4545
run: |
46-
task sdk:deps:cicd:iodc-token-generation >> $GITHUB_ENV
46+
task deps:cicd:iodc-token-generation >> $GITHUB_ENV
4747
4848
- name: Test Python SDK
4949
env:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ __pycache__/
1919
*.pyd
2020
.venv/
2121

22+
# Caches
23+
.pytest_cache
24+
.ruff_cache
25+
.mypy_cache
26+
2227
# MacOS
2328
.DS_Store
2429

Taskfile.yml

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,33 @@ tasks:
2626
# SDK tasks (top-level, user-facing)
2727
# ---------------------------------------------------------------------------
2828

29-
sdk:deps:cicd:iodc-token-generation:
30-
desc: Get Fulcio OIDC token for CICD
31-
requires:
32-
vars: [CLIENT_ID]
33-
cmds:
34-
- |
35-
OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
36-
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value')
37-
38-
CLIENT_ID="{{.CLIENT_ID}}"
39-
PROVIDER_URL="https://token.actions.githubusercontent.com"
40-
41-
echo "OIDC_PROVIDER_URL=${PROVIDER_URL}"
42-
echo "CLIENT_ID=${CLIENT_ID}"
43-
echo "OIDC_TOKEN=${OIDC_TOKEN}"
44-
45-
sdk:build:python:
29+
build:
4630
desc: Build python client SDK package
4731
deps:
48-
- task: sdk:deps:python
32+
- task: deps
4933
cmds:
5034
- "{{.UV_BIN}} build"
5135

52-
sdk:test-env:create:
36+
test-env:create:
5337
desc: Create Kubernetes cluster test environment
5438
cmds:
55-
- task: test-env:kubernetes:local
39+
- task: test-env:deploy
5640
vars:
5741
DIRECTORY_SERVER_PUBLICATION_SCHEDULER_INTERVAL: 1s
5842
DIRECTORY_SERVER_OASF_API_VALIDATION_SCHEMA_URL: "https://schema.oasf.outshift.com"
5943

60-
sdk:test-env:delete:
44+
test-env:delete:
6145
desc: Delete Kubernetes cluster test environment
6246
cmds:
63-
- task: test-env:kubernetes:local:cleanup
47+
- task: test-env:deploy:cleanup
6448

6549
sdk:test:python:
6650
desc: Test python client SDK package
6751
deps:
68-
- task: sdk:deps:python
52+
- task: deps
6953
cmds:
70-
- defer: { task: sdk:test-env:delete }
71-
- task: sdk:test-env:create
54+
- defer: { task: test-env:delete }
55+
- task: test-env:create
7256
- |
7357
export DIRCTL_IMAGE_TAG="{{ .DIRCTL_IMAGE_TAG }}"
7458
export COSIGN_PATH="$(printf "%s" "${COSIGN_PATH:-{{ .COSIGN_BIN }}}")"
@@ -81,7 +65,7 @@ tasks:
8165
'{{ .UV_BIN }}' sync
8266
'{{ .UV_BIN }}' run python example.py
8367
84-
sdk:deps:python:
68+
deps:
8569
desc: Install deps for python SDK package
8670
deps:
8771
- task: deps:uv
@@ -90,7 +74,7 @@ tasks:
9074
cmds:
9175
- "{{.UV_BIN}} sync --all-packages"
9276

93-
sdk:deps:python:example:
77+
deps:example:
9478
desc: Install deps for Python SDK example package
9579
internal: true
9680
dir: ./examples
@@ -99,13 +83,38 @@ tasks:
9983
cmds:
10084
- "{{.UV_BIN}} sync --all-packages"
10185

102-
sdk:tidy:
86+
deps:packages:
87+
desc: Install Python package dependencies (uv sync only)
88+
internal: true
89+
deps:
90+
- task: deps:uv
91+
cmds:
92+
- "{{.UV_BIN}} sync --all-packages"
93+
94+
lint:
95+
desc: Lint Python SDK (ruff check + format check + mypy)
96+
deps:
97+
- task: deps:packages
98+
cmds:
99+
- "{{.UV_BIN}} run ruff check dir-sdk-python examples"
100+
- "{{.UV_BIN}} run ruff format --check dir-sdk-python examples"
101+
- "{{.UV_BIN}} run mypy dir-sdk-python examples"
102+
103+
lint:fix:
104+
desc: Auto-fix Python lint and formatting (local dev)
105+
deps:
106+
- task: deps:packages
107+
cmds:
108+
- "{{.UV_BIN}} run ruff check --fix dir-sdk-python examples"
109+
- "{{.UV_BIN}} run ruff format dir-sdk-python examples"
110+
111+
deps:tidy:
103112
desc: Install deps for SDK packages
104113
cmds:
105-
- task: sdk:deps:python
106-
- task: sdk:deps:python:example
114+
- task: deps
115+
- task: deps:example
107116

108-
sdk:release:python:
117+
release:
109118
desc: Release python client SDK package
110119
env:
111120
UV_PUBLISH_TOKEN: "{{ .UV_PUBLISH_TOKEN }}"
@@ -124,7 +133,7 @@ tasks:
124133
# Test environment (kind cluster + helm deploy of dir-server)
125134
# ---------------------------------------------------------------------------
126135

127-
test-env:kubernetes:setup-cluster:
136+
test-env:setup-cluster:
128137
desc: Create a kind cluster and load Docker images
129138
deps:
130139
- deps:helm
@@ -162,11 +171,11 @@ tasks:
162171
{{ .KIND_BIN }} export kubeconfig --name {{ .KIND_CLUSTER_NAME }}
163172
{{ .KUBECTL_BIN }} cluster-info
164173
165-
test-env:kubernetes:local:
174+
test-env:deploy:
166175
aliases: [deploy:local]
167176
desc: Deploy a local Directory server in Kubernetes
168177
deps:
169-
- task: test-env:kubernetes:setup-cluster
178+
- task: test-env:setup-cluster
170179
vars:
171180
KIND_EXTRA_PORT_MAPPING: "{{.API_HOSTPORT}}:{{.API_NODEPORT}},{{.METRICS_HOSTPORT}}:{{.METRICS_NODEPORT}}"
172181
vars:
@@ -184,11 +193,11 @@ tasks:
184193
# TODO: make logic idempotent so that running functional tests does not change previous contexts
185194

186195
# Generate credentials and htpasswd file (using defaults)
187-
- task: test-env:kubernetes:gen-htpasswd-creds
196+
- task: test-env:htpasswd-creds:gen
188197

189198
# Cleanup credentials on exit (using defaults)
190199
- defer:
191-
task: test-env:kubernetes:cleanup-htpasswd-creds
200+
task: test-env:htpasswd-creds:cleanup
192201

193202
# Deploy chart
194203
- |
@@ -226,7 +235,7 @@ tasks:
226235
--wait-for-jobs \
227236
--timeout "15m"
228237
229-
test-env:kubernetes:local:cleanup:
238+
test-env:deploy:cleanup:
230239
desc: Cleanup Kubernetes environment for local deployment
231240
deps:
232241
- deps:kind
@@ -235,7 +244,7 @@ tasks:
235244
cmds:
236245
- "{{ .KIND_BIN }} delete cluster --name {{ .KIND_CLUSTER_NAME }}"
237246

238-
test-env:kubernetes:gen-htpasswd-creds:
247+
test-env:htpasswd-creds:gen:
239248
desc: Generate htpasswd credentials and files for DIR deployment
240249
vars:
241250
HTPASSWD_USERNAME: '{{ .HTPASSWD_USERNAME | default "apiserver" }}'
@@ -268,7 +277,7 @@ tasks:
268277
{{ .HTPASSWD_SYNC_USERNAME }}:${HTPASSWD_SYNC}
269278
EOF
270279
271-
test-env:kubernetes:cleanup-htpasswd-creds:
280+
test-env:htpasswd-creds:cleanup:
272281
desc: Cleanup htpasswd credentials and files
273282
vars:
274283
CREDS_FILE: '{{ .CREDS_FILE | default "/tmp/dir-htpasswd-creds.env" }}'
@@ -389,9 +398,25 @@ tasks:
389398
RENOVATE_OPTS_DEFAULT: >-
390399
--persist-repo-data 'true'
391400
--allowed-post-upgrade-commands '[".*"]'
392-
--post-upgrade-tasks '{"commands": ["task sdk:tidy"], "executionMode": "branch"}'
401+
--post-upgrade-tasks '{"commands": ["task deps:tidy"], "executionMode": "branch"}'
393402
RENOVATE_OPTS: "{{ .RENOVATE_OPTS | default .RENOVATE_OPTS_DEFAULT }}"
394403
RENOVATE_PLATFORM_EFFECTIVE:
395404
sh: printf '%s' "${RENOVATE_PLATFORM:-local}"
396405
cmds:
397406
- renovate {{ .RENOVATE_OPTS }} --platform "{{ .RENOVATE_PLATFORM_EFFECTIVE }}"
407+
408+
deps:cicd:iodc-token-generation:
409+
desc: Get Fulcio OIDC token for CICD
410+
requires:
411+
vars: [CLIENT_ID]
412+
cmds:
413+
- |
414+
OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
415+
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value')
416+
417+
CLIENT_ID="{{.CLIENT_ID}}"
418+
PROVIDER_URL="https://token.actions.githubusercontent.com"
419+
420+
echo "OIDC_PROVIDER_URL=${PROVIDER_URL}"
421+
echo "CLIENT_ID=${CLIENT_ID}"
422+
echo "OIDC_TOKEN=${OIDC_TOKEN}"
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright AGNTCY Contributors (https://github.com/agntcy)
22
# SPDX-License-Identifier: Apache-2.0
33

4+
from agntcy.dir_sdk.client.auth.oauth_pkce import OAuthPkceError
45
from agntcy.dir_sdk.client.client import Client
56
from agntcy.dir_sdk.client.config import Config
6-
from agntcy.dir_sdk.client.auth.oauth_pkce import OAuthPkceError as OAuthPkceError
7+
8+
__all__ = ["Client", "Config", "OAuthPkceError"]

dir-sdk-python/agntcy/dir_sdk/client/auth/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
"""Authentication/session helpers for the Directory client."""
55

66
from agntcy.dir_sdk.client.auth.oauth_pkce import OAuthTokenHolder
7-
from agntcy.dir_sdk.client.auth.session import OAuthSessionManager, cached_token_from_response
7+
from agntcy.dir_sdk.client.auth.session import (
8+
OAuthSessionManager,
9+
cached_token_from_response,
10+
)
811
from agntcy.dir_sdk.client.auth.token_cache import CachedToken, TokenCache
912

10-
__all__ = ["CachedToken", "OAuthSessionManager", "OAuthTokenHolder", "TokenCache", "cached_token_from_response"]
13+
__all__ = [
14+
"CachedToken",
15+
"OAuthSessionManager",
16+
"OAuthTokenHolder",
17+
"TokenCache",
18+
"cached_token_from_response",
19+
]

dir-sdk-python/agntcy/dir_sdk/client/auth/oauth_pkce.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
import threading
1111
import webbrowser
1212
from http.server import BaseHTTPRequestHandler, HTTPServer
13-
from typing import Any
13+
from typing import Any, cast
1414
from urllib.parse import parse_qs, urlencode, urlparse
1515

1616
import httpx
17-
from authlib.oauth2.rfc7636 import create_s256_code_challenge
18-
1917
from agntcy.dir_sdk.client.config import Config
18+
from authlib.oauth2.rfc7636 import create_s256_code_challenge
2019

2120
logger = logging.getLogger(__name__)
2221

@@ -44,7 +43,7 @@ def fetch_openid_configuration(
4443
with httpx.Client(verify=verify, timeout=timeout) as client:
4544
response = client.get(url)
4645
response.raise_for_status()
47-
data = response.json()
46+
data = cast(dict[str, Any], response.json())
4847
if "authorization_endpoint" not in data or "token_endpoint" not in data:
4948
msg = "OpenID configuration missing authorization_endpoint or token_endpoint"
5049
raise OAuthPkceError(msg)
@@ -70,7 +69,7 @@ def _form_post(
7069
detail = response.text[:500] if response.text else ""
7170
msg = f"Token HTTP {response.status_code}: {detail}"
7271
raise OAuthPkceError(msg) from e
73-
return response.json()
72+
return cast(dict[str, Any], response.json())
7473

7574

7675
class OAuthTokenHolder:
@@ -99,7 +98,7 @@ def get_access_token(self) -> str:
9998
"or call Client.authenticate_oauth_pkce()"
10099
)
101100
raise RuntimeError(msg)
102-
return self._access_token # type: ignore[return-value]
101+
return self._access_token
103102

104103

105104
def exchange_authorization_code(
@@ -184,7 +183,9 @@ def do_GET(self) -> None: # noqa: N802
184183
try:
185184
req = urlparse(self.path)
186185
if req.path != path:
187-
error_holder.append("redirect path does not match oidc_redirect_uri")
186+
error_holder.append(
187+
"redirect path does not match oidc_redirect_uri"
188+
)
188189
self.send_error(404, "Not Found")
189190
return
190191
qs = parse_qs(req.query)
@@ -213,9 +214,7 @@ def do_GET(self) -> None: # noqa: N802
213214

214215
def _ok_page(self, message: str) -> None:
215216
body = (
216-
"<!DOCTYPE html><html><body><p>"
217-
+ message
218-
+ "</p></body></html>"
217+
"<!DOCTYPE html><html><body><p>" + message + "</p></body></html>"
219218
).encode("utf-8")
220219
self.send_response(200)
221220
self.send_header("Content-Type", "text/html; charset=utf-8")

dir-sdk-python/agntcy/dir_sdk/client/auth/session.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@
77

88
from datetime import UTC, datetime, timedelta
99

10-
from agntcy.dir_sdk.client.config import Config
1110
from agntcy.dir_sdk.client.auth.oauth_pkce import (
1211
OAuthTokenHolder,
1312
fetch_openid_configuration,
1413
run_loopback_pkce_login,
1514
)
1615
from agntcy.dir_sdk.client.auth.token_cache import CachedToken, TokenCache
16+
from agntcy.dir_sdk.client.config import Config
1717

1818

19-
def cached_token_from_response(config: Config, payload: dict[str, object]) -> CachedToken:
19+
def cached_token_from_response(
20+
config: Config, payload: dict[str, object]
21+
) -> CachedToken:
2022
expires_at = None
2123
expires_in = payload.get("expires_in")
22-
if expires_in is not None:
24+
if isinstance(expires_in, (int, float, str)):
2325
expires_at = datetime.now(UTC) + timedelta(seconds=int(expires_in))
2426

2527
refresh_token = payload.get("refresh_token")

0 commit comments

Comments
 (0)