traffik 1.0.0b1__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.
- traffik-1.0.0b1/.coverage +0 -0
- traffik-1.0.0b1/.dockerignore +111 -0
- traffik-1.0.0b1/.github/CODEOWNERS +1 -0
- traffik-1.0.0b1/.github/workflows/code-quality.yaml +83 -0
- traffik-1.0.0b1/.github/workflows/enforce-pr-source.yaml +47 -0
- traffik-1.0.0b1/.github/workflows/publish.yaml +345 -0
- traffik-1.0.0b1/.github/workflows/test.yaml +207 -0
- traffik-1.0.0b1/.gitignore +9 -0
- traffik-1.0.0b1/CHANGELOG.md +0 -0
- traffik-1.0.0b1/CONTRIBUTING.md +461 -0
- traffik-1.0.0b1/DOCKER.md +258 -0
- traffik-1.0.0b1/Dockerfile +43 -0
- traffik-1.0.0b1/LICENSE +21 -0
- traffik-1.0.0b1/Makefile +175 -0
- traffik-1.0.0b1/PKG-INFO +1720 -0
- traffik-1.0.0b1/README.md +1631 -0
- traffik-1.0.0b1/TESTING.md +313 -0
- traffik-1.0.0b1/docker-compose.yml +221 -0
- traffik-1.0.0b1/docker-test.sh +259 -0
- traffik-1.0.0b1/pyproject.toml +110 -0
- traffik-1.0.0b1/src/traffik/__init__.py +21 -0
- traffik-1.0.0b1/src/traffik/backends/__init__.py +0 -0
- traffik-1.0.0b1/src/traffik/backends/base.py +536 -0
- traffik-1.0.0b1/src/traffik/backends/inmemory.py +398 -0
- traffik-1.0.0b1/src/traffik/backends/memcached.py +555 -0
- traffik-1.0.0b1/src/traffik/backends/redis.py +411 -0
- traffik-1.0.0b1/src/traffik/decorators.py +211 -0
- traffik-1.0.0b1/src/traffik/exceptions.py +131 -0
- traffik-1.0.0b1/src/traffik/middleware.py +240 -0
- traffik-1.0.0b1/src/traffik/py.typed +0 -0
- traffik-1.0.0b1/src/traffik/rates.py +195 -0
- traffik-1.0.0b1/src/traffik/strategies/__init__.py +7 -0
- traffik-1.0.0b1/src/traffik/strategies/fixed_window.py +133 -0
- traffik-1.0.0b1/src/traffik/strategies/leaky_bucket.py +256 -0
- traffik-1.0.0b1/src/traffik/strategies/sliding_window.py +257 -0
- traffik-1.0.0b1/src/traffik/strategies/token_bucket.py +333 -0
- traffik-1.0.0b1/src/traffik/throttles.py +322 -0
- traffik-1.0.0b1/src/traffik/types.py +137 -0
- traffik-1.0.0b1/src/traffik/utils.py +485 -0
- traffik-1.0.0b1/tests/__init__.py +0 -0
- traffik-1.0.0b1/tests/asyncio_client.py +354 -0
- traffik-1.0.0b1/tests/backends/__init__.py +1 -0
- traffik-1.0.0b1/tests/backends/test_base.py +72 -0
- traffik-1.0.0b1/tests/backends/test_concurrency.py +135 -0
- traffik-1.0.0b1/tests/backends/test_generics.py +790 -0
- traffik-1.0.0b1/tests/conftest.py +118 -0
- traffik-1.0.0b1/tests/fastapi/__init__.py +1 -0
- traffik-1.0.0b1/tests/fastapi/test_decorators.py +118 -0
- traffik-1.0.0b1/tests/fastapi/test_middleware.py +867 -0
- traffik-1.0.0b1/tests/fastapi/test_throttles.py +329 -0
- traffik-1.0.0b1/tests/fastapi/test_throttles_dynamic.py +265 -0
- traffik-1.0.0b1/tests/fastapi/test_throttles_multi_service.py +479 -0
- traffik-1.0.0b1/tests/starlette/__init__.py +0 -0
- traffik-1.0.0b1/tests/starlette/test_middleware.py +893 -0
- traffik-1.0.0b1/tests/starlette/test_throttles.py +312 -0
- traffik-1.0.0b1/tests/starlette/test_throttles_dynamic.py +277 -0
- traffik-1.0.0b1/tests/starlette/test_throttles_multi_service.py +504 -0
- traffik-1.0.0b1/tests/strategies/__init__.py +1 -0
- traffik-1.0.0b1/tests/strategies/test_edge_cases.py +411 -0
- traffik-1.0.0b1/tests/strategies/test_fixed_window.py +195 -0
- traffik-1.0.0b1/tests/strategies/test_leaky_bucket.py +345 -0
- traffik-1.0.0b1/tests/strategies/test_sliding_window.py +298 -0
- traffik-1.0.0b1/tests/strategies/test_token_bucket.py +362 -0
- traffik-1.0.0b1/tests/test_rates.py +303 -0
- traffik-1.0.0b1/tests/utils.py +11 -0
- traffik-1.0.0b1/uv.lock +1172 -0
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# .dockerignore for traffik library
|
|
2
|
+
|
|
3
|
+
# Git
|
|
4
|
+
.git
|
|
5
|
+
.gitignore
|
|
6
|
+
|
|
7
|
+
# Python cache
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
*.so
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
|
|
29
|
+
# Testing
|
|
30
|
+
.pytest_cache/
|
|
31
|
+
.coverage
|
|
32
|
+
htmlcov/
|
|
33
|
+
.tox/
|
|
34
|
+
.cache
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.dmypy.json
|
|
37
|
+
dmypy.json
|
|
38
|
+
|
|
39
|
+
# IDEs
|
|
40
|
+
.vscode/
|
|
41
|
+
.idea/
|
|
42
|
+
*.swp
|
|
43
|
+
*.swo
|
|
44
|
+
*~
|
|
45
|
+
|
|
46
|
+
# OS
|
|
47
|
+
.DS_Store
|
|
48
|
+
.DS_Store?
|
|
49
|
+
._*
|
|
50
|
+
.Spotlight-V100
|
|
51
|
+
.Trashes
|
|
52
|
+
ehthumbs.db
|
|
53
|
+
Thumbs.db
|
|
54
|
+
|
|
55
|
+
# Environments
|
|
56
|
+
.env
|
|
57
|
+
.venv
|
|
58
|
+
env/
|
|
59
|
+
venv/
|
|
60
|
+
ENV/
|
|
61
|
+
env.bak/
|
|
62
|
+
venv.bak/
|
|
63
|
+
|
|
64
|
+
# Jupyter Notebook
|
|
65
|
+
.ipynb_checkpoints
|
|
66
|
+
|
|
67
|
+
# Documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# CI/CD
|
|
71
|
+
.github/
|
|
72
|
+
|
|
73
|
+
# Docker
|
|
74
|
+
Dockerfile*
|
|
75
|
+
docker-compose*.yml
|
|
76
|
+
.dockerignore
|
|
77
|
+
|
|
78
|
+
# Package manager
|
|
79
|
+
node_modules/
|
|
80
|
+
npm-debug.log*
|
|
81
|
+
|
|
82
|
+
# Security
|
|
83
|
+
*.pem
|
|
84
|
+
*.key
|
|
85
|
+
|
|
86
|
+
# Logs
|
|
87
|
+
*.log
|
|
88
|
+
|
|
89
|
+
# Temporary files
|
|
90
|
+
tmp/
|
|
91
|
+
temp/
|
|
92
|
+
|
|
93
|
+
# Editor backups
|
|
94
|
+
*.bak
|
|
95
|
+
*.tmp
|
|
96
|
+
|
|
97
|
+
# Coverage reports
|
|
98
|
+
htmlcov/
|
|
99
|
+
.coverage.*
|
|
100
|
+
coverage.xml
|
|
101
|
+
*.cover
|
|
102
|
+
.hypothesis/
|
|
103
|
+
|
|
104
|
+
# Ruff cache
|
|
105
|
+
.ruff_cache/
|
|
106
|
+
|
|
107
|
+
# MyPy cache
|
|
108
|
+
.mypy_cache/
|
|
109
|
+
|
|
110
|
+
# UV cache
|
|
111
|
+
.uv_cache/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @tioluwa
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: Code Quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# push:
|
|
5
|
+
# branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
workflow_dispatch: # Allow manual triggering
|
|
9
|
+
workflow_call: # Allow referencing
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: code-quality-${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
quality:
|
|
17
|
+
name: Code Quality Checks
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout code
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v4
|
|
26
|
+
with:
|
|
27
|
+
version: "latest"
|
|
28
|
+
|
|
29
|
+
- name: Set up Python
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.9"
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: uv sync --extra dev
|
|
36
|
+
|
|
37
|
+
- name: Check code formatting
|
|
38
|
+
run: |
|
|
39
|
+
echo "Checking code formatting..."
|
|
40
|
+
uv run ruff format src/ tests/
|
|
41
|
+
|
|
42
|
+
- name: Run security analysis
|
|
43
|
+
run: |
|
|
44
|
+
echo "Running Bandit security analysis..."
|
|
45
|
+
uv run bandit -r src/ -f json -o bandit-report.json || true
|
|
46
|
+
uv run bandit -r src/
|
|
47
|
+
continue-on-error: false
|
|
48
|
+
|
|
49
|
+
- name: Upload security reports
|
|
50
|
+
if: always()
|
|
51
|
+
uses: actions/upload-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: security-reports
|
|
54
|
+
path: |
|
|
55
|
+
bandit-report.json
|
|
56
|
+
retention-days: 30
|
|
57
|
+
|
|
58
|
+
type-check:
|
|
59
|
+
name: Type Checking
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
|
|
62
|
+
steps:
|
|
63
|
+
- name: Checkout code
|
|
64
|
+
uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Install uv
|
|
67
|
+
uses: astral-sh/setup-uv@v4
|
|
68
|
+
with:
|
|
69
|
+
version: "latest"
|
|
70
|
+
|
|
71
|
+
- name: Set up Python
|
|
72
|
+
uses: actions/setup-python@v5
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.9"
|
|
75
|
+
|
|
76
|
+
- name: Install dependencies
|
|
77
|
+
run: uv sync --extra dev
|
|
78
|
+
|
|
79
|
+
- name: Run type checking
|
|
80
|
+
run: |
|
|
81
|
+
echo "Running mypy type checking..."
|
|
82
|
+
uv run python -m mypy src/ --ignore-missing-imports --no-strict-optional
|
|
83
|
+
continue-on-error: true # Optional since you might not have mypy setup yet
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Enforce PR Source Branch and Origin
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
enforce-branch-policy:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout code
|
|
12
|
+
uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0 # Required to access full history
|
|
15
|
+
|
|
16
|
+
- name: Add summary
|
|
17
|
+
run: |
|
|
18
|
+
echo "## 🔒 Branch Policy Enforcement" >> $GITHUB_STEP_SUMMARY
|
|
19
|
+
echo "- ✅ Only \`release/*\` and \`hotfix/*\` branches can PR into \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
20
|
+
echo "- ✅ \`release/*\` must originate from \`develop\`" >> $GITHUB_STEP_SUMMARY
|
|
21
|
+
echo "- ✅ \`hotfix/*\` must originate from \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
22
|
+
|
|
23
|
+
- name: Validate PR source branch and origin
|
|
24
|
+
run: |
|
|
25
|
+
SOURCE_BRANCH="${{ github.head_ref }}"
|
|
26
|
+
TARGET_BRANCH="${{ github.base_ref }}"
|
|
27
|
+
|
|
28
|
+
echo "🔍 Checking source: $SOURCE_BRANCH -> target: $TARGET_BRANCH"
|
|
29
|
+
|
|
30
|
+
if [[ "$SOURCE_BRANCH" == release/* ]]; then
|
|
31
|
+
BASE_BRANCH="develop"
|
|
32
|
+
elif [[ "$SOURCE_BRANCH" == hotfix/* ]]; then
|
|
33
|
+
BASE_BRANCH="main"
|
|
34
|
+
else
|
|
35
|
+
echo "❌ Invalid source branch name: must start with release/ or hotfix/"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
git fetch origin $BASE_BRANCH
|
|
40
|
+
MERGE_BASE=$(git merge-base origin/$SOURCE_BRANCH origin/$BASE_BRANCH)
|
|
41
|
+
|
|
42
|
+
if [[ "$(git rev-parse origin/$BASE_BRANCH)" != "$MERGE_BASE" ]]; then
|
|
43
|
+
echo "❌ $SOURCE_BRANCH must be based on $BASE_BRANCH"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "✅ $SOURCE_BRANCH is valid and based on $BASE_BRANCH"
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*' # Trigger on version tags like v0.1.0, v1.0.0, etc.
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch: # Allow manual triggering
|
|
10
|
+
inputs:
|
|
11
|
+
test_only:
|
|
12
|
+
description: 'Publish to TestPyPI only'
|
|
13
|
+
type: boolean
|
|
14
|
+
default: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
# Run code quality workflow
|
|
18
|
+
quality-gate:
|
|
19
|
+
name: Code Quality Assurance Gate
|
|
20
|
+
uses: ./.github/workflows/code-quality.yaml
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Run the full test suite first
|
|
24
|
+
test-gate:
|
|
25
|
+
name: Test Suite Gate
|
|
26
|
+
uses: ./.github/workflows/test.yaml
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Build and validate package
|
|
30
|
+
build:
|
|
31
|
+
name: Build Package
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
needs: [quality-gate, test-gate]
|
|
34
|
+
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout code
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- name: Install uv
|
|
40
|
+
uses: astral-sh/setup-uv@v4
|
|
41
|
+
with:
|
|
42
|
+
version: "latest"
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.11"
|
|
48
|
+
|
|
49
|
+
- name: Verify version consistency
|
|
50
|
+
run: |
|
|
51
|
+
# Extract version from pyproject.toml and git tag
|
|
52
|
+
PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
53
|
+
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
|
54
|
+
TAG_VERSION=${GITHUB_REF_NAME#v}
|
|
55
|
+
if [[ "$PROJECT_VERSION" != "$TAG_VERSION" ]]; then
|
|
56
|
+
echo "Version mismatch: pyproject.toml has $PROJECT_VERSION but tag is $TAG_VERSION"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
echo "Version: $PROJECT_VERSION"
|
|
61
|
+
|
|
62
|
+
- name: Build package
|
|
63
|
+
run: uv build
|
|
64
|
+
|
|
65
|
+
- name: Validate build artifacts
|
|
66
|
+
run: |
|
|
67
|
+
ls -la dist/
|
|
68
|
+
# Verify both wheel and sdist are created
|
|
69
|
+
test -f dist/*.whl || (echo "No wheel found" && exit 1)
|
|
70
|
+
test -f dist/*.tar.gz || (echo "No source distribution found" && exit 1)
|
|
71
|
+
|
|
72
|
+
# Check package contents
|
|
73
|
+
uv run python -m zipfile -l dist/*.whl
|
|
74
|
+
uv run python -m tarfile -l dist/*.tar.gz
|
|
75
|
+
|
|
76
|
+
echo "✅ Build artifacts created successfully"
|
|
77
|
+
|
|
78
|
+
- name: Install and test built package
|
|
79
|
+
run: |
|
|
80
|
+
# Install the wheel in a clean environment
|
|
81
|
+
uv venv test-env
|
|
82
|
+
uv pip install dist/*.whl
|
|
83
|
+
uv run python -c "
|
|
84
|
+
import traffik
|
|
85
|
+
from traffik.backends.inmemory import InMemoryBackend
|
|
86
|
+
from traffik.throttles import HTTPThrottle
|
|
87
|
+
print('✅ Built package works correctly')
|
|
88
|
+
"
|
|
89
|
+
|
|
90
|
+
- name: Upload build artifacts
|
|
91
|
+
uses: actions/upload-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: dist-${{ github.sha }}
|
|
94
|
+
path: dist/
|
|
95
|
+
retention-days: 30
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Publish to TestPyPI for testing
|
|
99
|
+
publish-testpypi:
|
|
100
|
+
name: Publish to TestPyPI
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
needs: build
|
|
103
|
+
if: >
|
|
104
|
+
github.event_name == 'workflow_dispatch' ||
|
|
105
|
+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
|
|
106
|
+
github.event_name == 'release'
|
|
107
|
+
|
|
108
|
+
environment:
|
|
109
|
+
name: testpypi
|
|
110
|
+
url: https://test.pypi.org/p/traffik
|
|
111
|
+
|
|
112
|
+
outputs:
|
|
113
|
+
published: ${{ steps.publish-step.outputs.published || steps.skip-step.outputs.published || 'false' }}
|
|
114
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
115
|
+
|
|
116
|
+
steps:
|
|
117
|
+
- name: Checkout code
|
|
118
|
+
uses: actions/checkout@v4
|
|
119
|
+
|
|
120
|
+
- name: Set up Python
|
|
121
|
+
uses: actions/setup-python@v5
|
|
122
|
+
with:
|
|
123
|
+
python-version: "3.11"
|
|
124
|
+
|
|
125
|
+
- name: Install uv
|
|
126
|
+
uses: astral-sh/setup-uv@v4
|
|
127
|
+
with:
|
|
128
|
+
version: "latest"
|
|
129
|
+
|
|
130
|
+
- name: Install jq for version parsing
|
|
131
|
+
run: uv pip install jq --system
|
|
132
|
+
|
|
133
|
+
- name: Get project version
|
|
134
|
+
id: get-version
|
|
135
|
+
run: |
|
|
136
|
+
VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
137
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
138
|
+
|
|
139
|
+
- name: Check if version already exists on TestPyPI
|
|
140
|
+
id: version-check
|
|
141
|
+
run: |
|
|
142
|
+
VERSION="${{ steps.get-version.outputs.version }}"
|
|
143
|
+
if curl -sSf https://test.pypi.org/pypi/traffik/json | jq -e ".releases[\"$VERSION\"]" > /dev/null; then
|
|
144
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
145
|
+
else
|
|
146
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
- name: Skip publishing (version exists)
|
|
150
|
+
if: steps.version-check.outputs.exists == 'true'
|
|
151
|
+
run: echo "⚠️ Version already exists on TestPyPI. Skipping publish."
|
|
152
|
+
|
|
153
|
+
- name: Download build artifacts
|
|
154
|
+
uses: actions/download-artifact@v4
|
|
155
|
+
with:
|
|
156
|
+
name: dist-${{ github.sha }}
|
|
157
|
+
path: dist/
|
|
158
|
+
|
|
159
|
+
- name: Publish to TestPyPI
|
|
160
|
+
id: publish-step
|
|
161
|
+
if: steps.version-check.outputs.exists == 'false'
|
|
162
|
+
run: |
|
|
163
|
+
uv publish --index testpypi
|
|
164
|
+
echo "published=true" >> "$GITHUB_OUTPUT"
|
|
165
|
+
env:
|
|
166
|
+
UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
167
|
+
|
|
168
|
+
- name: Mark as skipped
|
|
169
|
+
if: steps.version-check.outputs.exists == 'true'
|
|
170
|
+
id: skip-step
|
|
171
|
+
run: echo "published=false" >> "$GITHUB_OUTPUT"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Verify TestPyPI installation
|
|
175
|
+
verify-testpypi:
|
|
176
|
+
name: Verify TestPyPI Installation
|
|
177
|
+
runs-on: ubuntu-latest
|
|
178
|
+
needs: publish-testpypi
|
|
179
|
+
if: needs.publish-testpypi.result == 'success' && needs.publish-testpypi.outputs.published == 'true'
|
|
180
|
+
continue-on-error: true
|
|
181
|
+
|
|
182
|
+
strategy:
|
|
183
|
+
matrix:
|
|
184
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
185
|
+
|
|
186
|
+
steps:
|
|
187
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
188
|
+
uses: actions/setup-python@v5
|
|
189
|
+
with:
|
|
190
|
+
python-version: ${{ matrix.python-version }}
|
|
191
|
+
|
|
192
|
+
- name: Wait for TestPyPI availability
|
|
193
|
+
run: |
|
|
194
|
+
echo "⏳ Waiting for package to be available on TestPyPI..."
|
|
195
|
+
sleep 60 # Give TestPyPI time to process
|
|
196
|
+
|
|
197
|
+
- name: Install from TestPyPI
|
|
198
|
+
run: |
|
|
199
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
200
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
201
|
+
traffik[all] --prefer-binary
|
|
202
|
+
|
|
203
|
+
- name: Test installation and functionality
|
|
204
|
+
run: |
|
|
205
|
+
python -c "
|
|
206
|
+
import asyncio
|
|
207
|
+
import traffik
|
|
208
|
+
from traffik.backends.inmemory import InMemoryBackend
|
|
209
|
+
from traffik.throttles import HTTPThrottle
|
|
210
|
+
|
|
211
|
+
print('✅ Package imported successfully')
|
|
212
|
+
print(f'Version: {getattr(traffik, \"__version__\", \"unknown\")}')
|
|
213
|
+
|
|
214
|
+
async def test():
|
|
215
|
+
backend = InMemoryBackend()
|
|
216
|
+
async with backend():
|
|
217
|
+
throttle = HTTPThrottle(limit=5, seconds=60)
|
|
218
|
+
print('✅ Basic functionality works')
|
|
219
|
+
|
|
220
|
+
asyncio.run(test())
|
|
221
|
+
"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# Publish to PyPI (production)
|
|
225
|
+
publish-pypi:
|
|
226
|
+
name: Publish to PyPI
|
|
227
|
+
runs-on: ubuntu-latest
|
|
228
|
+
needs: [build, publish-testpypi]
|
|
229
|
+
if: >
|
|
230
|
+
github.event_name == 'release' &&
|
|
231
|
+
github.event.action == 'published' &&
|
|
232
|
+
!github.event.release.prerelease &&
|
|
233
|
+
(github.event.inputs.test_only != 'true' || github.event.inputs.test_only == '') &&
|
|
234
|
+
always() &&
|
|
235
|
+
needs.build.result == 'success' &&
|
|
236
|
+
needs.publish-testpypi.result == 'success'
|
|
237
|
+
|
|
238
|
+
environment:
|
|
239
|
+
name: pypi
|
|
240
|
+
url: https://pypi.org/p/traffik
|
|
241
|
+
|
|
242
|
+
steps:
|
|
243
|
+
- name: Checkout code
|
|
244
|
+
uses: actions/checkout@v4
|
|
245
|
+
|
|
246
|
+
- name: Set up Python
|
|
247
|
+
uses: actions/setup-python@v5
|
|
248
|
+
with:
|
|
249
|
+
python-version: "3.11"
|
|
250
|
+
|
|
251
|
+
- name: Install uv
|
|
252
|
+
uses: astral-sh/setup-uv@v4
|
|
253
|
+
with:
|
|
254
|
+
version: "latest"
|
|
255
|
+
|
|
256
|
+
- name: Install jq for version parsing
|
|
257
|
+
run: uv pip install jq --system
|
|
258
|
+
|
|
259
|
+
- name: Get project version
|
|
260
|
+
id: get-version
|
|
261
|
+
run: |
|
|
262
|
+
VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
263
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
264
|
+
|
|
265
|
+
- name: Check if version already exists on PyPI
|
|
266
|
+
id: version-check
|
|
267
|
+
run: |
|
|
268
|
+
VERSION="${{ steps.get-version.outputs.version }}"
|
|
269
|
+
if curl -sSf https://pypi.org/pypi/traffik/json | jq -e ".releases[\"$VERSION\"]" > /dev/null; then
|
|
270
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
271
|
+
else
|
|
272
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
- name: Skip publishing (version exists)
|
|
276
|
+
if: steps.version-check.outputs.exists == 'true'
|
|
277
|
+
run: echo "⚠️ Version already exists on PyPI. Skipping publish."
|
|
278
|
+
|
|
279
|
+
- name: Download build artifacts
|
|
280
|
+
uses: actions/download-artifact@v4
|
|
281
|
+
with:
|
|
282
|
+
name: dist-${{ github.sha }}
|
|
283
|
+
path: dist/
|
|
284
|
+
|
|
285
|
+
- name: Final validation before PyPI
|
|
286
|
+
if: steps.version-check.outputs.exists == 'false'
|
|
287
|
+
run: |
|
|
288
|
+
echo "🚀 Publishing to PyPI..."
|
|
289
|
+
ls -la dist/
|
|
290
|
+
|
|
291
|
+
# Double-check we're not publishing a dev version
|
|
292
|
+
if uv run python -c "import tomllib; version=tomllib.load(open('pyproject.toml', 'rb'))['project']['version']; exit(1 if 'dev' in version or 'alpha' in version or 'beta' in version or 'rc' in version else 0)"; then
|
|
293
|
+
echo "✅ Version looks stable for PyPI"
|
|
294
|
+
else
|
|
295
|
+
echo "❌ Version appears to be a pre-release"
|
|
296
|
+
exit 1
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
- name: Publish to PyPI
|
|
300
|
+
if: steps.version-check.outputs.exists == 'false'
|
|
301
|
+
run: uv publish
|
|
302
|
+
env:
|
|
303
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Post-publish verification
|
|
307
|
+
verify-pypi:
|
|
308
|
+
name: Verify PyPI Installation
|
|
309
|
+
runs-on: ubuntu-latest
|
|
310
|
+
needs: publish-pypi
|
|
311
|
+
if: success()
|
|
312
|
+
|
|
313
|
+
steps:
|
|
314
|
+
- name: Set up Python
|
|
315
|
+
uses: actions/setup-python@v5
|
|
316
|
+
with:
|
|
317
|
+
python-version: "3.11"
|
|
318
|
+
|
|
319
|
+
- name: Wait for PyPI availability
|
|
320
|
+
run: |
|
|
321
|
+
echo "⏳ Waiting for package to be available on PyPI..."
|
|
322
|
+
sleep 120 # Give PyPI time to process
|
|
323
|
+
|
|
324
|
+
- name: Install from PyPI
|
|
325
|
+
run: pip install traffik[all]
|
|
326
|
+
|
|
327
|
+
- name: Verify installation
|
|
328
|
+
run: |
|
|
329
|
+
python -c "
|
|
330
|
+
import traffik
|
|
331
|
+
print(f'✅🎉 Successfully installed traffik {getattr(traffik, \"__version__\", \"unknown\")} from PyPI')
|
|
332
|
+
"
|
|
333
|
+
|
|
334
|
+
- name: Create release summary
|
|
335
|
+
run: |
|
|
336
|
+
echo "## 🎉 Release Summary" >> $GITHUB_STEP_SUMMARY
|
|
337
|
+
echo "- ✅ Quality checks passed" >> $GITHUB_STEP_SUMMARY
|
|
338
|
+
echo "- ✅ All tests passed" >> $GITHUB_STEP_SUMMARY
|
|
339
|
+
echo "- ✅ Security checks passed" >> $GITHUB_STEP_SUMMARY
|
|
340
|
+
echo "- ✅ Package built successfully" >> $GITHUB_STEP_SUMMARY
|
|
341
|
+
echo "- ✅ Published to TestPyPI" >> $GITHUB_STEP_SUMMARY
|
|
342
|
+
echo "- ✅ TestPyPI installation verified" >> $GITHUB_STEP_SUMMARY
|
|
343
|
+
echo "- ✅ Published to PyPI" >> $GITHUB_STEP_SUMMARY
|
|
344
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
345
|
+
echo "📦 Package is now available: \`pip install traffik\`" >> $GITHUB_STEP_SUMMARY
|