platform-fastapicommon 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. platform_fastapicommon-0.1.0/.dockerignore +29 -0
  2. platform_fastapicommon-0.1.0/.env-example +33 -0
  3. platform_fastapicommon-0.1.0/.github/CODEOWNERS +10 -0
  4. platform_fastapicommon-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  5. platform_fastapicommon-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
  6. platform_fastapicommon-0.1.0/.github/SECURITY.md +32 -0
  7. platform_fastapicommon-0.1.0/.github/dependabot.yml +22 -0
  8. platform_fastapicommon-0.1.0/.github/pull_request_template.md +59 -0
  9. platform_fastapicommon-0.1.0/.github/workflows/ci.yml +334 -0
  10. platform_fastapicommon-0.1.0/.github/workflows/quality.yml +54 -0
  11. platform_fastapicommon-0.1.0/.github/workflows/release.yml +208 -0
  12. platform_fastapicommon-0.1.0/.github/workflows/test.yml +39 -0
  13. platform_fastapicommon-0.1.0/.github/workflows/validate.yml +33 -0
  14. platform_fastapicommon-0.1.0/.gitignore +221 -0
  15. platform_fastapicommon-0.1.0/.pre-commit-config.yaml +37 -0
  16. platform_fastapicommon-0.1.0/ARCHITECTURE.md +752 -0
  17. platform_fastapicommon-0.1.0/CHANGELOG.md +120 -0
  18. platform_fastapicommon-0.1.0/CONTRIBUTING.md +145 -0
  19. platform_fastapicommon-0.1.0/Dockerfile +49 -0
  20. platform_fastapicommon-0.1.0/Makefile +195 -0
  21. platform_fastapicommon-0.1.0/PKG-INFO +733 -0
  22. platform_fastapicommon-0.1.0/README.md +688 -0
  23. platform_fastapicommon-0.1.0/SECURITY.md +65 -0
  24. platform_fastapicommon-0.1.0/VERSIONING.md +201 -0
  25. platform_fastapicommon-0.1.0/docker/grafana/provisioning/datasources/datasources.yaml +39 -0
  26. platform_fastapicommon-0.1.0/docker/loki/loki.yaml +29 -0
  27. platform_fastapicommon-0.1.0/docker/otel-collector/config.yaml +30 -0
  28. platform_fastapicommon-0.1.0/docker/prometheus/prometheus.yml +8 -0
  29. platform_fastapicommon-0.1.0/docker/prometheus/prometheus.yml.tpl +8 -0
  30. platform_fastapicommon-0.1.0/docker/promtail/promtail.yaml +30 -0
  31. platform_fastapicommon-0.1.0/docker/tempo/tempo.yaml +26 -0
  32. platform_fastapicommon-0.1.0/docker-compose.yml +86 -0
  33. platform_fastapicommon-0.1.0/docs/Architecture.md +8 -0
  34. platform_fastapicommon-0.1.0/docs/MigrationGuide.md +26 -0
  35. platform_fastapicommon-0.1.0/docs/adr/ADR-001-logging.md +25 -0
  36. platform_fastapicommon-0.1.0/docs/adr/ADR-002-tracing.md +26 -0
  37. platform_fastapicommon-0.1.0/docs/adr/ADR-003-metrics.md +32 -0
  38. platform_fastapicommon-0.1.0/docs/adr/ADR-004-context-propagation.md +30 -0
  39. platform_fastapicommon-0.1.0/docs/architecture/README.md +21 -0
  40. platform_fastapicommon-0.1.0/docs/architecture/mermaid/bootstrap-flow.mmd +28 -0
  41. platform_fastapicommon-0.1.0/docs/architecture/mermaid/consuming-service-wiring.mmd +20 -0
  42. platform_fastapicommon-0.1.0/docs/architecture/mermaid/documentation-assets.mmd +34 -0
  43. platform_fastapicommon-0.1.0/docs/architecture/mermaid/exception-handling-flow.mmd +27 -0
  44. platform_fastapicommon-0.1.0/docs/architecture/mermaid/grpc-interceptors.mmd +29 -0
  45. platform_fastapicommon-0.1.0/docs/architecture/mermaid/metrics-flow.mmd +30 -0
  46. platform_fastapicommon-0.1.0/docs/architecture/mermaid/module-dependencies.mmd +60 -0
  47. platform_fastapicommon-0.1.0/docs/architecture/mermaid/observability-stack.mmd +31 -0
  48. platform_fastapicommon-0.1.0/docs/architecture/mermaid/rbac-dependency-flow.mmd +26 -0
  49. platform_fastapicommon-0.1.0/docs/architecture/mermaid/request-flow.mmd +33 -0
  50. platform_fastapicommon-0.1.0/docs/architecture/mermaid/tracing-initialization.mmd +19 -0
  51. platform_fastapicommon-0.1.0/examples/__init__.py +0 -0
  52. platform_fastapicommon-0.1.0/examples/fastapi_service/__init__.py +0 -0
  53. platform_fastapicommon-0.1.0/examples/fastapi_service/main.py +43 -0
  54. platform_fastapicommon-0.1.0/examples/grpc_service/__init__.py +0 -0
  55. platform_fastapicommon-0.1.0/examples/grpc_service/server.py +52 -0
  56. platform_fastapicommon-0.1.0/pyproject.toml +105 -0
  57. platform_fastapicommon-0.1.0/src/fastapicommon/__init__.py +15 -0
  58. platform_fastapicommon-0.1.0/src/fastapicommon/auth/__init__.py +24 -0
  59. platform_fastapicommon-0.1.0/src/fastapicommon/auth/dependencies.py +23 -0
  60. platform_fastapicommon-0.1.0/src/fastapicommon/auth/headers.py +15 -0
  61. platform_fastapicommon-0.1.0/src/fastapicommon/bootstrap/__init__.py +3 -0
  62. platform_fastapicommon-0.1.0/src/fastapicommon/bootstrap/app.py +66 -0
  63. platform_fastapicommon-0.1.0/src/fastapicommon/config/__init__.py +3 -0
  64. platform_fastapicommon-0.1.0/src/fastapicommon/config/settings.py +26 -0
  65. platform_fastapicommon-0.1.0/src/fastapicommon/context/__init__.py +28 -0
  66. platform_fastapicommon-0.1.0/src/fastapicommon/context/models.py +46 -0
  67. platform_fastapicommon-0.1.0/src/fastapicommon/context/vars.py +79 -0
  68. platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/__init__.py +19 -0
  69. platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/errors.py +50 -0
  70. platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/handlers.py +51 -0
  71. platform_fastapicommon-0.1.0/src/fastapicommon/grpc/__init__.py +4 -0
  72. platform_fastapicommon-0.1.0/src/fastapicommon/grpc/client.py +63 -0
  73. platform_fastapicommon-0.1.0/src/fastapicommon/grpc/server.py +95 -0
  74. platform_fastapicommon-0.1.0/src/fastapicommon/health/__init__.py +3 -0
  75. platform_fastapicommon-0.1.0/src/fastapicommon/health/router.py +44 -0
  76. platform_fastapicommon-0.1.0/src/fastapicommon/logging/__init__.py +4 -0
  77. platform_fastapicommon-0.1.0/src/fastapicommon/logging/config.py +52 -0
  78. platform_fastapicommon-0.1.0/src/fastapicommon/logging/logger.py +13 -0
  79. platform_fastapicommon-0.1.0/src/fastapicommon/metrics/__init__.py +19 -0
  80. platform_fastapicommon-0.1.0/src/fastapicommon/metrics/definitions.py +33 -0
  81. platform_fastapicommon-0.1.0/src/fastapicommon/metrics/middleware.py +31 -0
  82. platform_fastapicommon-0.1.0/src/fastapicommon/metrics/router.py +12 -0
  83. platform_fastapicommon-0.1.0/src/fastapicommon/middleware/__init__.py +4 -0
  84. platform_fastapicommon-0.1.0/src/fastapicommon/middleware/context.py +55 -0
  85. platform_fastapicommon-0.1.0/src/fastapicommon/middleware/timeout.py +28 -0
  86. platform_fastapicommon-0.1.0/src/fastapicommon/propagation/__init__.py +3 -0
  87. platform_fastapicommon-0.1.0/src/fastapicommon/propagation/client.py +48 -0
  88. platform_fastapicommon-0.1.0/src/fastapicommon/rbac/__init__.py +3 -0
  89. platform_fastapicommon-0.1.0/src/fastapicommon/rbac/dependencies.py +49 -0
  90. platform_fastapicommon-0.1.0/src/fastapicommon/telemetry/__init__.py +7 -0
  91. platform_fastapicommon-0.1.0/src/fastapicommon/telemetry/tracing.py +44 -0
  92. platform_fastapicommon-0.1.0/tests/auth/test_auth.py +42 -0
  93. platform_fastapicommon-0.1.0/tests/bootstrap/test_bootstrap.py +81 -0
  94. platform_fastapicommon-0.1.0/tests/config/test_config.py +43 -0
  95. platform_fastapicommon-0.1.0/tests/context/test_context.py +78 -0
  96. platform_fastapicommon-0.1.0/tests/exceptions/test_exceptions.py +88 -0
  97. platform_fastapicommon-0.1.0/tests/grpc/test_grpc.py +153 -0
  98. platform_fastapicommon-0.1.0/tests/health/test_health.py +38 -0
  99. platform_fastapicommon-0.1.0/tests/logging/test_logging.py +41 -0
  100. platform_fastapicommon-0.1.0/tests/metrics/test_metrics.py +51 -0
  101. platform_fastapicommon-0.1.0/tests/middleware/test_context_middleware.py +69 -0
  102. platform_fastapicommon-0.1.0/tests/middleware/test_timeout_middleware.py +35 -0
  103. platform_fastapicommon-0.1.0/tests/propagation/test_propagation.py +43 -0
  104. platform_fastapicommon-0.1.0/tests/rbac/test_rbac.py +41 -0
  105. platform_fastapicommon-0.1.0/tests/telemetry/test_telemetry.py +15 -0
  106. platform_fastapicommon-0.1.0/tests/test_package.py +10 -0
@@ -0,0 +1,29 @@
1
+ .git
2
+ .github
3
+ .claude
4
+ .cursor
5
+ .venv
6
+ .env
7
+ .env.*
8
+ !.env-example
9
+ logs
10
+ *.log
11
+ coverage.*
12
+ .coverage
13
+ htmlcov
14
+ .pytest_cache
15
+ .mypy_cache
16
+ .ruff_cache
17
+ __pycache__
18
+ *.pyc
19
+ dist
20
+ build
21
+ *.egg-info
22
+ # test code and tooling config are not needed at build/runtime
23
+ tests/
24
+ *.md
25
+ !README.md
26
+ Makefile
27
+ docker-compose.yml
28
+ docker/prometheus/prometheus.yml
29
+ docs/
@@ -0,0 +1,33 @@
1
+ # Copy to .env for local development: cp .env-example .env
2
+
3
+ # --- Application (examples/fastapi_service) ---
4
+ APP_NAME=platform-fastapicommon-example
5
+ APP_ENV=dev
6
+ APP_PORT=8080
7
+ BUILD_VERSION=dev
8
+
9
+ # --- OpenTelemetry ---
10
+ # Set to reach the local otel-collector started by `make docker-up` (OTLP/gRPC).
11
+ # Leave unset to record spans without exporting them.
12
+ OTLP_ENDPOINT=localhost:4317
13
+ ENABLE_TRACING=true
14
+ ENABLE_METRICS=true
15
+
16
+ # --- Local logs -> Loki (Promtail tails this file when using make docker-up) ---
17
+ LOG_FILE=logs/app.log
18
+
19
+ # --- Observability stack (Docker host port mappings) ---
20
+ OTEL_COLLECTOR_GRPC_PORT=4317
21
+ OTEL_COLLECTOR_HTTP_PORT=4318
22
+ TEMPO_HTTP_PORT=3200
23
+ LOKI_HTTP_PORT=3100
24
+ PROMETHEUS_HTTP_PORT=9090
25
+ GRAFANA_HTTP_PORT=3000
26
+ GRAFANA_ADMIN_USER=admin
27
+ GRAFANA_ADMIN_PASSWORD=admin
28
+
29
+ # Prometheus scrapes the example app on the host, not in the compose network.
30
+ PROMETHEUS_SCRAPE_TARGET=host.docker.internal:8080
31
+
32
+ # --- Tooling ---
33
+ PYTHON=python3
@@ -0,0 +1,10 @@
1
+ # Default: every file requires review from the platform team.
2
+ # Replace @BCBP-SOLUTIONS-FZC-LLC/platform-team with your actual team slug.
3
+ * @BCBP-SOLUTIONS-FZC-LLC/platform-team
4
+
5
+ # Workflow and release automation changes require explicit sign-off.
6
+ .github/workflows/ @BCBP-SOLUTIONS-FZC-LLC/platform-team
7
+ .github/dependabot.yml @BCBP-SOLUTIONS-FZC-LLC/platform-team
8
+
9
+ # Public API surface — extra scrutiny.
10
+ src/fastapicommon/ @BCBP-SOLUTIONS-FZC-LLC/platform-team
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a defect in fastapicommon middleware or helpers
4
+ title: '[BUG] '
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Description
10
+ A clear description of the bug.
11
+
12
+ ## Library version
13
+ `fastapicommon==X.Y.Z`
14
+
15
+ ## Python version
16
+ `python --version` output
17
+
18
+ ## Steps to reproduce
19
+ 1.
20
+ 2.
21
+ 3.
22
+
23
+ ## Expected behaviour
24
+ What you expected to happen.
25
+
26
+ ## Actual behaviour
27
+ What actually happened. Include error messages, stack traces, or log output.
28
+
29
+ ## Minimal reproduction
30
+ ```python
31
+ # paste the smallest Python snippet that triggers the bug
32
+ ```
33
+
34
+ ## Additional context
35
+ Any other relevant context (OS, container environment, related issues).
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a new middleware, helper, or enhancement
4
+ title: '[FEAT] '
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Problem / motivation
10
+ What problem does this solve? Who is affected?
11
+
12
+ ## Proposed solution
13
+ Describe the API or behaviour change you'd like.
14
+
15
+ ```python
16
+ # Example usage of the proposed feature
17
+ ```
18
+
19
+ ## Alternatives considered
20
+ Other approaches you evaluated and why you ruled them out.
21
+
22
+ ## Acceptance criteria
23
+ - [ ]
24
+ - [ ]
25
+ - [ ]
26
+
27
+ ## Additional context
28
+ Links to related issues, RFCs, or prior art.
@@ -0,0 +1,32 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | 0.x | ✅ Active |
8
+
9
+ Older major versions are not patched. Upgrade to the latest 0.x release.
10
+
11
+ ## Reporting a Vulnerability
12
+
13
+ **Do not open a public GitHub issue for security vulnerabilities.**
14
+
15
+ Email: vijay@bcbpsolutions.com
16
+
17
+ Include in your report:
18
+ - Description of the vulnerability and affected component
19
+ - Steps to reproduce
20
+ - Potential impact (data exposure, privilege escalation, DoS, etc.)
21
+ - Suggested fix or patch (if any)
22
+
23
+ ### Response timeline
24
+
25
+ | Step | Target |
26
+ |------|--------|
27
+ | Initial acknowledgement | 48 hours |
28
+ | Severity assessment | 5 business days |
29
+ | Patch release (critical/high) | 14 days |
30
+ | Public disclosure | After patch ships |
31
+
32
+ We follow responsible disclosure. We will credit reporters in the release notes unless anonymity is requested.
@@ -0,0 +1,22 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ open-pull-requests-limit: 5
8
+ labels: [dependencies, python]
9
+
10
+ - package-ecosystem: github-actions
11
+ directory: /
12
+ schedule:
13
+ interval: weekly
14
+ open-pull-requests-limit: 5
15
+ labels: [dependencies, github-actions]
16
+
17
+ - package-ecosystem: docker
18
+ directory: /
19
+ schedule:
20
+ interval: weekly
21
+ open-pull-requests-limit: 5
22
+ labels: [dependencies, docker]
@@ -0,0 +1,59 @@
1
+ ## Description
2
+ Provide a clear description of the changes.
3
+
4
+ ---
5
+
6
+ ## Type of Change
7
+ - [ ] Bug fix
8
+ - [ ] New feature
9
+ - [ ] Refactor
10
+ - [ ] Documentation
11
+ - [ ] Test
12
+ - [ ] Breaking change
13
+
14
+ ---
15
+
16
+ ## Testing
17
+ - [ ] Unit tests added/updated
18
+ - [ ] All tests passing (`make test`)
19
+ - [ ] Manual testing performed (if required)
20
+
21
+ ---
22
+
23
+ ## Checklist
24
+
25
+ ### Code Quality
26
+ - [ ] Code is properly formatted (`make format` — ruff format + black)
27
+ - [ ] Linting passed (`make lint` — ruff)
28
+ - [ ] Type checking passed (`make lint` — mypy --strict)
29
+ - [ ] No debug logs / commented code
30
+
31
+ ---
32
+
33
+ ### Observability (IMPORTANT for your system)
34
+ - [ ] Logs include `request_id` / `correlation_id` (via `fastapicommon.logging.get_logger`, not stdlib `logging`)
35
+ - [ ] Errors use a `PlatformException` subclass, not a bare `HTTPException`
36
+ - [ ] Middleware ordering preserved if middleware changed (see `ARCHITECTURE.md#execution-model`)
37
+
38
+ ---
39
+
40
+ ### Security
41
+ - [ ] No secrets hardcoded
42
+ - [ ] Input validation handled
43
+ - [ ] RBAC (`RequireRole` / `RequireAnyRole` / `RequireAllRoles`) checked (if endpoint changes)
44
+
45
+ ---
46
+
47
+ ### Documentation
48
+ - [ ] README updated (if needed)
49
+ - [ ] `ARCHITECTURE.md` / ADRs updated (if a cross-cutting standard changed)
50
+
51
+ ---
52
+
53
+ ## 🔗 Related Issue
54
+ Closes #<issue-id>
55
+
56
+ ---
57
+
58
+ ## Deployment Notes
59
+ Mention anything important for deployment.
@@ -0,0 +1,334 @@
1
+ # CI for https://github.com/BCBP-SOLUTIONS-FZC-LLC/platform-fastapicommon
2
+ name: CI
3
+
4
+ "on":
5
+ push:
6
+ branches: [main, develop]
7
+ pull_request:
8
+ branches: [main, develop]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ validate:
16
+ name: Validate
17
+ uses: ./.github/workflows/validate.yml
18
+
19
+ coverage:
20
+ name: Coverage (Python ${{ matrix.python-version }})
21
+ runs-on: ubuntu-latest
22
+ needs: validate
23
+ strategy:
24
+ matrix:
25
+ python-version: ['3.11', '3.12', '3.13', '3.14']
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v7
29
+
30
+ - name: Set up Python
31
+ uses: actions/setup-python@v7
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+ cache: pip
35
+
36
+ - name: Install dependencies
37
+ run: pip install -e ".[dev,grpc,examples]"
38
+
39
+ - name: Test with coverage (>=90% required, enforced by pyproject.toml)
40
+ run: pytest
41
+
42
+ - name: Upload coverage report
43
+ uses: actions/upload-artifact@v7
44
+ if: always()
45
+ with:
46
+ name: coverage-report-py${{ matrix.python-version }}
47
+ path: coverage.xml
48
+ if-no-files-found: ignore
49
+
50
+ build:
51
+ name: Build
52
+ runs-on: ubuntu-latest
53
+ needs: coverage
54
+ steps:
55
+ - name: Checkout
56
+ uses: actions/checkout@v7
57
+
58
+ - name: Set up Python
59
+ uses: actions/setup-python@v7
60
+ with:
61
+ python-version: '3.12'
62
+ cache: pip
63
+
64
+ - name: Build wheel + sdist
65
+ run: |
66
+ pip install build
67
+ python -m build
68
+
69
+ - name: Verify build artifacts
70
+ run: ls dist/*.whl dist/*.tar.gz
71
+
72
+ - name: Upload build artifacts
73
+ uses: actions/upload-artifact@v7
74
+ with:
75
+ name: fastapicommon-dist
76
+ path: dist/
77
+ if-no-files-found: error
78
+
79
+ lint-dockerfile:
80
+ name: Lint Dockerfile
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - name: Checkout
84
+ uses: actions/checkout@v7
85
+
86
+ - name: Run hadolint
87
+ uses: hadolint/hadolint-action@v3.3.0
88
+ with:
89
+ dockerfile: Dockerfile
90
+ failure-threshold: warning
91
+
92
+ build-image:
93
+ name: Build image (cache)
94
+ runs-on: ubuntu-latest
95
+ steps:
96
+ - name: Checkout
97
+ uses: actions/checkout@v7
98
+
99
+ # Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
100
+ # times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
101
+ # known transient flake, not a config problem. Retry once with the same action
102
+ # before failing the job.
103
+ - name: Set up Docker Buildx
104
+ id: buildx
105
+ continue-on-error: true
106
+ uses: docker/setup-buildx-action@v4
107
+
108
+ - name: Set up Docker Buildx (retry)
109
+ if: steps.buildx.outcome == 'failure'
110
+ uses: docker/setup-buildx-action@v4
111
+
112
+ - name: Build (no push)
113
+ uses: docker/build-push-action@v7
114
+ with:
115
+ context: .
116
+ file: ./Dockerfile
117
+ push: false
118
+ tags: platform-fastapicommon-example:ci
119
+ cache-from: type=gha
120
+ cache-to: type=gha,mode=max
121
+
122
+ trivy-scan:
123
+ name: Trivy CVE scan
124
+ runs-on: ubuntu-latest
125
+ needs: build-image
126
+ steps:
127
+ - name: Checkout
128
+ uses: actions/checkout@v7
129
+
130
+ # Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
131
+ # times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
132
+ # known transient flake, not a config problem. Retry once with the same action
133
+ # before failing the job.
134
+ - name: Set up Docker Buildx
135
+ id: buildx
136
+ continue-on-error: true
137
+ uses: docker/setup-buildx-action@v4
138
+
139
+ - name: Set up Docker Buildx (retry)
140
+ if: steps.buildx.outcome == 'failure'
141
+ uses: docker/setup-buildx-action@v4
142
+
143
+ # Rebuilds against the GHA cache populated by "Build image (cache)" — fast, and
144
+ # keeps this job self-contained (no cross-job image tarball transfer needed).
145
+ - name: Build image (load, cached)
146
+ uses: docker/build-push-action@v7
147
+ with:
148
+ context: .
149
+ file: ./Dockerfile
150
+ push: false
151
+ load: true
152
+ tags: platform-fastapicommon-example:ci
153
+ cache-from: type=gha
154
+
155
+ - name: Run Trivy vulnerability scan
156
+ uses: aquasecurity/trivy-action@v0.36.0
157
+ with:
158
+ image-ref: platform-fastapicommon-example:ci
159
+ format: table
160
+ severity: HIGH,CRITICAL
161
+ ignore-unfixed: true
162
+ exit-code: '1'
163
+ scanners: vuln
164
+
165
+ smoke-tests:
166
+ name: Smoke tests
167
+ runs-on: ubuntu-latest
168
+ needs: build-image
169
+ steps:
170
+ - name: Checkout
171
+ uses: actions/checkout@v7
172
+
173
+ # Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
174
+ # times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
175
+ # known transient flake, not a config problem. Retry once with the same action
176
+ # before failing the job.
177
+ - name: Set up Docker Buildx
178
+ id: buildx
179
+ continue-on-error: true
180
+ uses: docker/setup-buildx-action@v4
181
+
182
+ - name: Set up Docker Buildx (retry)
183
+ if: steps.buildx.outcome == 'failure'
184
+ uses: docker/setup-buildx-action@v4
185
+
186
+ - name: Build image (load, cached)
187
+ uses: docker/build-push-action@v7
188
+ with:
189
+ context: .
190
+ file: ./Dockerfile
191
+ push: false
192
+ load: true
193
+ tags: platform-fastapicommon-example:ci
194
+ cache-from: type=gha
195
+
196
+ - name: Run container
197
+ run: docker run -d --name smoke-test -p 8080:8080 platform-fastapicommon-example:ci
198
+
199
+ - name: Wait for health check
200
+ run: |
201
+ for i in $(seq 1 30); do
202
+ if curl -fsS http://localhost:8080/health >/dev/null 2>&1; then
203
+ echo "healthy after ${i}s"
204
+ exit 0
205
+ fi
206
+ sleep 1
207
+ done
208
+ echo "Service did not become healthy in time" >&2
209
+ docker logs smoke-test
210
+ exit 1
211
+
212
+ - name: Smoke test endpoints
213
+ run: |
214
+ set -euo pipefail
215
+ curl -fsS http://localhost:8080/health | grep -q '"status":"ok"'
216
+ curl -fsS http://localhost:8080/ready | grep -q '"status":"ok"'
217
+ curl -fsS http://localhost:8080/metrics | grep -q 'http_requests_total'
218
+ curl -fsS http://localhost:8080/widgets/me | grep -q '"user_id":null'
219
+ echo "All smoke checks passed"
220
+
221
+ - name: Container logs
222
+ if: always()
223
+ run: docker logs smoke-test || true
224
+
225
+ - name: Stop container
226
+ if: always()
227
+ run: docker rm -f smoke-test || true
228
+
229
+ docker:
230
+ name: Docker image (example service)
231
+ runs-on: ubuntu-latest
232
+ needs: build
233
+ if: github.event_name == 'push'
234
+ permissions:
235
+ contents: read
236
+ packages: write
237
+ steps:
238
+ - name: Checkout
239
+ uses: actions/checkout@v7
240
+
241
+ - name: Log in to GHCR
242
+ uses: docker/login-action@v4
243
+ with:
244
+ registry: ghcr.io
245
+ username: ${{ github.actor }}
246
+ password: ${{ secrets.GITHUB_TOKEN }}
247
+
248
+ - name: Docker metadata
249
+ id: meta
250
+ uses: docker/metadata-action@v6
251
+ with:
252
+ images: ghcr.io/bcbp-solutions-fzc-llc/platform-fastapicommon
253
+ tags: |
254
+ type=ref,event=branch
255
+ type=sha,prefix=sha-,format=short
256
+
257
+ # Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
258
+ # times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
259
+ # known transient flake, not a config problem. Retry once with the same action
260
+ # before failing the job.
261
+ - name: Set up Docker Buildx
262
+ id: buildx
263
+ continue-on-error: true
264
+ uses: docker/setup-buildx-action@v4
265
+
266
+ - name: Set up Docker Buildx (retry)
267
+ if: steps.buildx.outcome == 'failure'
268
+ uses: docker/setup-buildx-action@v4
269
+
270
+ - name: Build and push image
271
+ uses: docker/build-push-action@v7
272
+ with:
273
+ context: .
274
+ file: ./Dockerfile
275
+ push: true
276
+ tags: ${{ steps.meta.outputs.tags }}
277
+ build-args: BUILD_VERSION=dev-${{ github.sha }}
278
+ cache-from: type=gha
279
+ cache-to: type=gha,mode=max
280
+
281
+ pr-summary:
282
+ name: PR summary
283
+ runs-on: ubuntu-latest
284
+ needs: [validate, build-image, lint-dockerfile, trivy-scan, smoke-tests]
285
+ if: always() && github.event_name == 'pull_request'
286
+ permissions:
287
+ pull-requests: write
288
+ steps:
289
+ - name: Post summary comment
290
+ uses: actions/github-script@v9
291
+ with:
292
+ script: |
293
+ const results = {
294
+ 'Validate (Quality + Test)': '${{ needs.validate.result }}',
295
+ 'Build image (cache)': '${{ needs.build-image.result }}',
296
+ 'Lint Dockerfile': '${{ needs.lint-dockerfile.result }}',
297
+ 'Trivy CVE scan': '${{ needs.trivy-scan.result }}',
298
+ 'Smoke tests': '${{ needs.smoke-tests.result }}',
299
+ };
300
+ const icon = (r) => (r === 'success' ? '✅' : r === 'skipped' ? '⏭️' : '❌');
301
+ const rows = Object.entries(results)
302
+ .map(([name, result]) => `| ${name} | ${icon(result)} ${result} |`)
303
+ .join('\n');
304
+ const body = [
305
+ '### CI summary',
306
+ '',
307
+ '| Check | Result |',
308
+ '|---|---|',
309
+ rows,
310
+ ].join('\n');
311
+
312
+ const marker = '<!-- pr-summary-bot -->';
313
+ const fullBody = `${marker}\n${body}`;
314
+ const { data: comments } = await github.rest.issues.listComments({
315
+ owner: context.repo.owner,
316
+ repo: context.repo.repo,
317
+ issue_number: context.issue.number,
318
+ });
319
+ const existing = comments.find((c) => c.body.includes(marker));
320
+ if (existing) {
321
+ await github.rest.issues.updateComment({
322
+ owner: context.repo.owner,
323
+ repo: context.repo.repo,
324
+ comment_id: existing.id,
325
+ body: fullBody,
326
+ });
327
+ } else {
328
+ await github.rest.issues.createComment({
329
+ owner: context.repo.owner,
330
+ repo: context.repo.repo,
331
+ issue_number: context.issue.number,
332
+ body: fullBody,
333
+ });
334
+ }
@@ -0,0 +1,54 @@
1
+ # Reusable workflow — called by validate.yml's "Quality" job. Its job id ("quality")
2
+ # combines with the calling chain to produce the "Validate / Quality / quality" required
3
+ # status check.
4
+ # Runs: checkout → Python setup → install → pip check → format check → ruff → mypy → pip-audit
5
+ name: Quality Check
6
+
7
+ "on":
8
+ workflow_call:
9
+ inputs:
10
+ ref:
11
+ description: 'Git ref to check out (leave empty to use the caller ref)'
12
+ type: string
13
+ required: false
14
+ default: ''
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ quality:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v7
25
+ with:
26
+ ref: ${{ inputs.ref || github.ref }}
27
+
28
+ - name: Set up Python
29
+ uses: actions/setup-python@v7
30
+ with:
31
+ python-version: '3.12'
32
+ cache: pip
33
+
34
+ - name: Install dependencies
35
+ run: pip install -e ".[dev,grpc,examples]"
36
+
37
+ - name: Verify dependency resolution
38
+ run: pip check
39
+
40
+ - name: Format check (ruff format + black --check)
41
+ run: |
42
+ ruff format --check src tests examples
43
+ black --check src tests examples
44
+
45
+ - name: Lint (ruff)
46
+ run: ruff check src tests examples
47
+
48
+ - name: Type check (mypy --strict)
49
+ run: mypy
50
+
51
+ - name: Vulnerability check (pip-audit)
52
+ run: |
53
+ pip install pip-audit
54
+ pip-audit