afterpython 0.0.1.dev2__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
- afterpython/__init__.py +13 -0
- afterpython/_io/toml.py +105 -0
- afterpython/_io/yaml.py +26 -0
- afterpython/_paths.py +39 -0
- afterpython/_typing.py +4 -0
- afterpython/builders/__init__.py +17 -0
- afterpython/builders/content_json.py +131 -0
- afterpython/builders/index_md.py +94 -0
- afterpython/builders/jupyter_notebook.py +293 -0
- afterpython/builders/markdown.py +271 -0
- afterpython/builders/metadata.py +50 -0
- afterpython/cli/__init__.py +3 -0
- afterpython/cli/commands/add.py +67 -0
- afterpython/cli/commands/build.py +260 -0
- afterpython/cli/commands/bump.py +118 -0
- afterpython/cli/commands/check.py +36 -0
- afterpython/cli/commands/clean.py +85 -0
- afterpython/cli/commands/commit.py +52 -0
- afterpython/cli/commands/commitizen.py +32 -0
- afterpython/cli/commands/dev.py +213 -0
- afterpython/cli/commands/format.py +28 -0
- afterpython/cli/commands/init.py +107 -0
- afterpython/cli/commands/init_branch_rules.py +9 -0
- afterpython/cli/commands/install.py +28 -0
- afterpython/cli/commands/lock.py +23 -0
- afterpython/cli/commands/pre_commit.py +66 -0
- afterpython/cli/commands/preview.py +22 -0
- afterpython/cli/commands/release.py +67 -0
- afterpython/cli/commands/remove.py +67 -0
- afterpython/cli/commands/start.py +144 -0
- afterpython/cli/commands/sync.py +152 -0
- afterpython/cli/commands/update.py +176 -0
- afterpython/cli/main.py +132 -0
- afterpython/const.py +4 -0
- afterpython/main.py +25 -0
- afterpython/pcu.py +166 -0
- afterpython/templates/ci-workflow-template.yml +197 -0
- afterpython/templates/cz-template.toml +7 -0
- afterpython/templates/dependabot-template.yml +30 -0
- afterpython/templates/deploy-workflow-template.yml +88 -0
- afterpython/templates/pre-commit-config-template.yaml +48 -0
- afterpython/templates/release-workflow-template.yml +56 -0
- afterpython/templates/ruff-template.toml +32 -0
- afterpython/tools/_afterpython.py +128 -0
- afterpython/tools/_git.py +137 -0
- afterpython/tools/branch_rules.py +106 -0
- afterpython/tools/commitizen.py +13 -0
- afterpython/tools/github_actions.py +48 -0
- afterpython/tools/myst.py +208 -0
- afterpython/tools/pre_commit.py +37 -0
- afterpython/tools/pyproject.py +110 -0
- afterpython/utils.py +299 -0
- afterpython/version.py +39 -0
- afterpython-0.1.0.dist-info/METADATA +131 -0
- afterpython-0.1.0.dist-info/RECORD +57 -0
- afterpython-0.1.0.dist-info/WHEEL +4 -0
- afterpython-0.1.0.dist-info/entry_points.txt +4 -0
- afterpython/README.py +0 -158
- afterpython/images/favicon.ico +0 -0
- afterpython/images/logo-trace.svg +0 -3
- afterpython/images/logo.png +0 -0
- afterpython/images/logo.svg +0 -2
- afterpython-0.0.1.dev2.dist-info/LICENSE +0 -201
- afterpython-0.0.1.dev2.dist-info/METADATA +0 -20
- afterpython-0.0.1.dev2.dist-info/RECORD +0 -9
- afterpython-0.0.1.dev2.dist-info/WHEEL +0 -4
afterpython/pcu.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pcu = "pip check updates", similar to ncu (npm check updates in Node.js)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, NamedTuple, TypeAlias, TypedDict
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from tomlkit.toml_document import TOMLDocument
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
|
|
14
|
+
from packaging.requirements import Requirement
|
|
15
|
+
|
|
16
|
+
from afterpython.version import Version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Dependency(NamedTuple):
|
|
20
|
+
requirement: Requirement
|
|
21
|
+
min_version: Version | None
|
|
22
|
+
max_version: Version | None = None
|
|
23
|
+
latest_version: Version | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DependencyName: TypeAlias = str
|
|
27
|
+
ExtrasName: TypeAlias = str
|
|
28
|
+
GroupName: TypeAlias = str
|
|
29
|
+
FakeCategoryName: TypeAlias = str
|
|
30
|
+
Dependencies = TypedDict(
|
|
31
|
+
"Dependencies",
|
|
32
|
+
{
|
|
33
|
+
"dependencies": dict[FakeCategoryName, list[Dependency]],
|
|
34
|
+
"optional-dependencies": dict[ExtrasName, list[Dependency]],
|
|
35
|
+
"dependency-groups": dict[GroupName, list[Dependency]],
|
|
36
|
+
"build-system": dict[str, list[Dependency]],
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_min_max_versions_from_requirement(
|
|
42
|
+
req: Requirement,
|
|
43
|
+
) -> dict[str, Version | None]:
|
|
44
|
+
min_ver: Version | None = None
|
|
45
|
+
max_ver: Version | None = None
|
|
46
|
+
if req.specifier:
|
|
47
|
+
# req.specifier is like ">=8.3.0" or ">=1.0,<2.0"
|
|
48
|
+
versions = sorted(Version(spec.version) for spec in req.specifier)
|
|
49
|
+
if len(versions) == 1:
|
|
50
|
+
min_ver = versions[0]
|
|
51
|
+
else:
|
|
52
|
+
min_ver, max_ver = versions[0], versions[-1]
|
|
53
|
+
return {
|
|
54
|
+
"min_version": min_ver,
|
|
55
|
+
"max_version": max_ver,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def get_latest_versions(
|
|
60
|
+
requirements: list[Requirement],
|
|
61
|
+
) -> dict[str, Version | None]:
|
|
62
|
+
"""Get latest versions for a list of dependencies from PyPI."""
|
|
63
|
+
import httpx
|
|
64
|
+
|
|
65
|
+
from afterpython.utils import fetch_pypi_json
|
|
66
|
+
|
|
67
|
+
async def fetch_version(
|
|
68
|
+
client: httpx.AsyncClient, package_name: str
|
|
69
|
+
) -> Version | None:
|
|
70
|
+
"""Fetch the latest version of a package from PyPI."""
|
|
71
|
+
data = await fetch_pypi_json(client, package_name)
|
|
72
|
+
return Version(data["info"]["version"]) if data else None
|
|
73
|
+
|
|
74
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
75
|
+
tasks = [fetch_version(client, req.name) for req in requirements]
|
|
76
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
77
|
+
return dict(zip([req.name for req in requirements], results, strict=False))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_dependencies() -> Dependencies:
|
|
81
|
+
"""Get dependencies from pyproject.toml"""
|
|
82
|
+
from afterpython.tools.pyproject import read_pyproject
|
|
83
|
+
|
|
84
|
+
doc: TOMLDocument = read_pyproject()
|
|
85
|
+
dependencies = {
|
|
86
|
+
"dependencies": list(doc["project"].get("dependencies", [])),
|
|
87
|
+
"optional-dependencies": dict(doc["project"].get("optional-dependencies", {})),
|
|
88
|
+
"dependency-groups": dict(doc.get("dependency-groups", {})),
|
|
89
|
+
"build-system": dict(doc.get("build-system", {})),
|
|
90
|
+
}
|
|
91
|
+
# add "fake_category" to "dependencies" to have the same structure as "optional-dependencies" and "dependency-groups"
|
|
92
|
+
dependencies["dependencies"] = {"fake_category": dependencies["dependencies"]}
|
|
93
|
+
# only keep the "requires" key
|
|
94
|
+
if "requires" in dependencies["build-system"]:
|
|
95
|
+
dependencies["build-system"] = {
|
|
96
|
+
"requires": dependencies["build-system"]["requires"]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# convert all dependency strings to type "Requirement"
|
|
100
|
+
for dep_type in dependencies:
|
|
101
|
+
for category, deps in dependencies[dep_type].items():
|
|
102
|
+
dependencies[dep_type][category] = [Requirement(dep) for dep in deps]
|
|
103
|
+
|
|
104
|
+
# flatten the dependencies to a list of type "Requirement"
|
|
105
|
+
all_reqs = [
|
|
106
|
+
req
|
|
107
|
+
for deps_dict in dependencies.values()
|
|
108
|
+
for req_list in deps_dict.values()
|
|
109
|
+
for req in req_list
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# Fetch ALL latest versions in ONE async call
|
|
113
|
+
latest_versions = asyncio.run(get_latest_versions(all_reqs))
|
|
114
|
+
|
|
115
|
+
# convert the requirements to type "Dependency"
|
|
116
|
+
for dep_type in dependencies:
|
|
117
|
+
for category, requirements in dependencies[dep_type].items():
|
|
118
|
+
dependencies[dep_type][category] = [
|
|
119
|
+
Dependency(
|
|
120
|
+
**parse_min_max_versions_from_requirement(req),
|
|
121
|
+
requirement=req,
|
|
122
|
+
latest_version=latest_versions.get(req.name),
|
|
123
|
+
)
|
|
124
|
+
for req in requirements
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
return dependencies
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def update_dependencies(dependencies: Dependencies):
|
|
131
|
+
"""Update dependency versions in pyproject.toml"""
|
|
132
|
+
from afterpython.tools.pyproject import read_pyproject, write_pyproject
|
|
133
|
+
|
|
134
|
+
doc: TOMLDocument = read_pyproject()
|
|
135
|
+
for dep_type in dependencies:
|
|
136
|
+
# category = extras or group name
|
|
137
|
+
for category, deps in dependencies[dep_type].items():
|
|
138
|
+
if dep_type == "dependencies":
|
|
139
|
+
doc_deps = doc["project"][dep_type]
|
|
140
|
+
elif dep_type == "optional-dependencies":
|
|
141
|
+
doc_deps = doc["project"][dep_type][category]
|
|
142
|
+
elif dep_type in ["dependency-groups", "build-system"]:
|
|
143
|
+
doc_deps = doc[dep_type][category]
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"Invalid dependency type: {dep_type}")
|
|
146
|
+
|
|
147
|
+
# Update in place
|
|
148
|
+
for i, (dep, package) in enumerate(zip(deps, doc_deps, strict=False)):
|
|
149
|
+
package: str # package = e.g. "click>=8.3.0"
|
|
150
|
+
req: Requirement = dep.requirement
|
|
151
|
+
min_ver = str(dep.min_version) if dep.min_version else None
|
|
152
|
+
max_ver = str(dep.max_version) if dep.max_version else None
|
|
153
|
+
latest_ver = str(dep.latest_version) if dep.latest_version else None
|
|
154
|
+
if (
|
|
155
|
+
req.name in package
|
|
156
|
+
and min_ver
|
|
157
|
+
and latest_ver
|
|
158
|
+
and min_ver in package
|
|
159
|
+
):
|
|
160
|
+
doc_deps[i] = package.replace(min_ver, latest_ver)
|
|
161
|
+
if max_ver and dep.latest_version not in req.specifier:
|
|
162
|
+
doc_deps[i] = doc_deps[i].replace(
|
|
163
|
+
max_ver, str(dep.max_version.next_breaking())
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
write_pyproject(doc)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Trigger CI on pushes to main and all pull requests
|
|
4
|
+
# This ensures every code change is validated before merging
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
pull_request:
|
|
10
|
+
# Run on all PRs regardless of target branch
|
|
11
|
+
branches:
|
|
12
|
+
- '**'
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
# Job 1: Lint and Format Check
|
|
16
|
+
# Runs ruff to check code quality and formatting
|
|
17
|
+
lint:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout code
|
|
21
|
+
uses: actions/checkout@v6
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v6
|
|
25
|
+
with:
|
|
26
|
+
python-version: '3.11'
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v7
|
|
30
|
+
with:
|
|
31
|
+
version: "latest"
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
# Install the package with uv in system mode
|
|
35
|
+
# This installs ruff and other tools defined in dependencies
|
|
36
|
+
run: uv pip install --system .
|
|
37
|
+
|
|
38
|
+
- name: Run ruff check
|
|
39
|
+
run: ruff check --config ./afterpython/ruff.toml .
|
|
40
|
+
|
|
41
|
+
- name: Run ruff format check
|
|
42
|
+
# --check flag ensures it only validates, doesn't reformat
|
|
43
|
+
run: ruff format --check --config ./afterpython/ruff.toml .
|
|
44
|
+
|
|
45
|
+
# Job 2: Detect Test Strategy
|
|
46
|
+
# Determines whether to use pixi or uv based on pixi.toml presence
|
|
47
|
+
detect-test-strategy:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
outputs:
|
|
50
|
+
use_pixi: ${{ steps.check.outputs.use_pixi }}
|
|
51
|
+
steps:
|
|
52
|
+
- name: Checkout code
|
|
53
|
+
uses: actions/checkout@v6
|
|
54
|
+
|
|
55
|
+
- name: Check for pixi.toml
|
|
56
|
+
id: check
|
|
57
|
+
run: |
|
|
58
|
+
if [ -f "pixi.toml" ]; then
|
|
59
|
+
echo "use_pixi=true" >> $GITHUB_OUTPUT
|
|
60
|
+
else
|
|
61
|
+
echo "use_pixi=false" >> $GITHUB_OUTPUT
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Job 3.1: Test Suite - UV Workflow
|
|
65
|
+
# Traditional Python testing with uv for projects without pixi
|
|
66
|
+
test-uv:
|
|
67
|
+
needs: detect-test-strategy
|
|
68
|
+
if: needs.detect-test-strategy.outputs.use_pixi == 'false'
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
strategy:
|
|
71
|
+
matrix:
|
|
72
|
+
# TODO: support python 3.14
|
|
73
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
74
|
+
fail-fast: false
|
|
75
|
+
|
|
76
|
+
steps:
|
|
77
|
+
- name: Checkout code
|
|
78
|
+
uses: actions/checkout@v6
|
|
79
|
+
|
|
80
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
81
|
+
uses: actions/setup-python@v6
|
|
82
|
+
with:
|
|
83
|
+
python-version: ${{ matrix.python-version }}
|
|
84
|
+
|
|
85
|
+
- name: Install uv
|
|
86
|
+
uses: astral-sh/setup-uv@v7
|
|
87
|
+
with:
|
|
88
|
+
version: "latest"
|
|
89
|
+
|
|
90
|
+
- name: Install dependencies
|
|
91
|
+
run: |
|
|
92
|
+
# Install package, main dependencies, and test dependencies
|
|
93
|
+
# If no [dependency-groups] test exists, only the package and main dependencies are installed
|
|
94
|
+
uv pip install --system . --group test || uv pip install --system .
|
|
95
|
+
|
|
96
|
+
- name: Run tests with pytest
|
|
97
|
+
# Run pytest with verbose output
|
|
98
|
+
# Exit code 5 (no tests collected) is treated as success
|
|
99
|
+
run: |
|
|
100
|
+
pytest -v || if [ $? -eq 5 ]; then exit 0; else exit $?; fi
|
|
101
|
+
|
|
102
|
+
# Job 3.2: Test Suite - Pixi Workflow
|
|
103
|
+
# Pixi-based testing for projects using pixi.toml
|
|
104
|
+
test-pixi:
|
|
105
|
+
needs: detect-test-strategy
|
|
106
|
+
if: needs.detect-test-strategy.outputs.use_pixi == 'true'
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
strategy:
|
|
109
|
+
matrix:
|
|
110
|
+
# TODO: support python 3.14
|
|
111
|
+
environment: ['py311', 'py312', 'py313']
|
|
112
|
+
fail-fast: false
|
|
113
|
+
|
|
114
|
+
steps:
|
|
115
|
+
- name: Checkout code
|
|
116
|
+
uses: actions/checkout@v6
|
|
117
|
+
|
|
118
|
+
- name: Set up pixi
|
|
119
|
+
uses: prefix-dev/setup-pixi@v0.9.3
|
|
120
|
+
with:
|
|
121
|
+
pixi-version: v0.60.0
|
|
122
|
+
cache: true
|
|
123
|
+
# Optional: Uncomment if you need prefix.dev authentication
|
|
124
|
+
# auth-host: prefix.dev
|
|
125
|
+
# auth-token: ${{ secrets.PREFIX_DEV_TOKEN }}
|
|
126
|
+
|
|
127
|
+
- name: Run tests with pixi
|
|
128
|
+
# Runs test in specific Python environment (py311, py312, py313)
|
|
129
|
+
# Assumes you have environments defined in pixi.toml with a "test" task
|
|
130
|
+
# Example pixi.toml:
|
|
131
|
+
# [environments]
|
|
132
|
+
# py311 = ["py311", "test"]
|
|
133
|
+
# [feature.test.tasks]
|
|
134
|
+
# test = "pytest -v"
|
|
135
|
+
# Note: Exit code 5 (no tests collected) is treated as success
|
|
136
|
+
run: |
|
|
137
|
+
pixi run -e ${{ matrix.environment }} test || if [ $? -eq 5 ]; then exit 0; else exit $?; fi
|
|
138
|
+
|
|
139
|
+
# Job 3.3: Test Suite - Complete
|
|
140
|
+
# Unified test status for branch protection
|
|
141
|
+
test:
|
|
142
|
+
if: always()
|
|
143
|
+
needs: [detect-test-strategy, test-uv, test-pixi]
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
steps:
|
|
146
|
+
- name: Check test results
|
|
147
|
+
run: |
|
|
148
|
+
uv_result="${{ needs.test-uv.result }}"
|
|
149
|
+
pixi_result="${{ needs.test-pixi.result }}"
|
|
150
|
+
# One should succeed, the other should be skipped
|
|
151
|
+
if [ "$uv_result" == "success" ] || [ "$uv_result" == "skipped" ]; then
|
|
152
|
+
if [ "$pixi_result" == "success" ] || [ "$pixi_result" == "skipped" ]; then
|
|
153
|
+
echo "All tests passed!"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
echo "Tests failed: uv=$uv_result, pixi=$pixi_result"
|
|
158
|
+
exit 1
|
|
159
|
+
|
|
160
|
+
# Job 4: Build Verification
|
|
161
|
+
# Ensures the package can be built successfully
|
|
162
|
+
build:
|
|
163
|
+
runs-on: ubuntu-latest
|
|
164
|
+
steps:
|
|
165
|
+
- name: Checkout code
|
|
166
|
+
uses: actions/checkout@v6
|
|
167
|
+
|
|
168
|
+
- name: Set up Python
|
|
169
|
+
uses: actions/setup-python@v6
|
|
170
|
+
with:
|
|
171
|
+
python-version: '3.11'
|
|
172
|
+
|
|
173
|
+
- name: Install uv
|
|
174
|
+
uses: astral-sh/setup-uv@v7
|
|
175
|
+
with:
|
|
176
|
+
version: "latest"
|
|
177
|
+
|
|
178
|
+
- name: Build package
|
|
179
|
+
run: uv build
|
|
180
|
+
|
|
181
|
+
- name: List build artifacts
|
|
182
|
+
# Show what was built (wheel + sdist)
|
|
183
|
+
# Useful for debugging build issues
|
|
184
|
+
run: ls -lh dist/
|
|
185
|
+
|
|
186
|
+
- name: Install built package
|
|
187
|
+
# Test that the built wheel can be installed
|
|
188
|
+
# This catches packaging issues like missing files
|
|
189
|
+
run: uv pip install --system dist/*.whl
|
|
190
|
+
|
|
191
|
+
- name: Verify installation
|
|
192
|
+
# Confirm the CLI entry point works
|
|
193
|
+
# This ensures the package is usable after installation
|
|
194
|
+
run: ap --help
|
|
195
|
+
|
|
196
|
+
# Job 5: Type Check
|
|
197
|
+
# typecheck:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Dependabot configuration for automatic dependency updates
|
|
2
|
+
# See: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
3
|
+
|
|
4
|
+
version: 2
|
|
5
|
+
updates:
|
|
6
|
+
# Update GitHub Actions versions
|
|
7
|
+
# This keeps your workflow actions secure and up-to-date
|
|
8
|
+
- package-ecosystem: "github-actions"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
# Check for updates monthly (less noisy than weekly)
|
|
12
|
+
interval: "monthly"
|
|
13
|
+
labels:
|
|
14
|
+
- "dependencies"
|
|
15
|
+
- "github-actions"
|
|
16
|
+
# Group all GitHub Actions updates into a single PR
|
|
17
|
+
# This reduces PR noise and makes reviewing easier
|
|
18
|
+
groups:
|
|
19
|
+
github-actions:
|
|
20
|
+
patterns:
|
|
21
|
+
- "*"
|
|
22
|
+
# Automatically add reviewers (optional - uncomment and customize)
|
|
23
|
+
# reviewers:
|
|
24
|
+
# - "your-username"
|
|
25
|
+
# Limit how many Dependabot PRs can be open at once (optional)
|
|
26
|
+
# Default is 5, increase if you want more concurrent PRs
|
|
27
|
+
# open-pull-requests-limit: 10
|
|
28
|
+
#
|
|
29
|
+
# Note: Auto-merge is configured in GitHub repo settings, not here.
|
|
30
|
+
# Go to: Settings → General → Pull Requests → Allow auto-merge
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Deploy Project Website to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main # Deploy when pushing to main branch
|
|
7
|
+
paths:
|
|
8
|
+
# Trigger deployment only when content or website files change
|
|
9
|
+
- 'afterpython/doc/**'
|
|
10
|
+
- 'afterpython/blog/**'
|
|
11
|
+
- 'afterpython/tutorial/**'
|
|
12
|
+
- 'afterpython/example/**'
|
|
13
|
+
- 'afterpython/guide/**'
|
|
14
|
+
- 'afterpython/static/**'
|
|
15
|
+
- 'afterpython/_website/**'
|
|
16
|
+
- 'afterpython/authors.yml'
|
|
17
|
+
- 'afterpython/faq.yml'
|
|
18
|
+
- '.github/workflows/deploy.yml'
|
|
19
|
+
workflow_dispatch: # Allow manual deployment from Actions tab
|
|
20
|
+
|
|
21
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
|
|
27
|
+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
28
|
+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
29
|
+
concurrency:
|
|
30
|
+
group: "pages"
|
|
31
|
+
cancel-in-progress: false
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
build:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout code
|
|
38
|
+
uses: actions/checkout@v6
|
|
39
|
+
|
|
40
|
+
- name: Install pnpm
|
|
41
|
+
uses: pnpm/action-setup@v4
|
|
42
|
+
with:
|
|
43
|
+
version: 10
|
|
44
|
+
|
|
45
|
+
- name: Set up Node.js
|
|
46
|
+
uses: actions/setup-node@v6
|
|
47
|
+
with:
|
|
48
|
+
node-version: '24'
|
|
49
|
+
cache: 'pnpm'
|
|
50
|
+
cache-dependency-path: './afterpython/_website/pnpm-lock.yaml'
|
|
51
|
+
|
|
52
|
+
- name: Set up Python
|
|
53
|
+
uses: actions/setup-python@v6
|
|
54
|
+
with:
|
|
55
|
+
python-version: '3.11'
|
|
56
|
+
|
|
57
|
+
- name: Install uv
|
|
58
|
+
uses: astral-sh/setup-uv@v7
|
|
59
|
+
with:
|
|
60
|
+
version: "latest"
|
|
61
|
+
|
|
62
|
+
- name: Install afterpython
|
|
63
|
+
run: uv pip install --system afterpython
|
|
64
|
+
|
|
65
|
+
- name: Install website dependencies
|
|
66
|
+
run: pnpm install
|
|
67
|
+
working-directory: ./afterpython/_website
|
|
68
|
+
|
|
69
|
+
- name: Build website
|
|
70
|
+
run: |
|
|
71
|
+
ap build
|
|
72
|
+
|
|
73
|
+
- name: Upload artifact
|
|
74
|
+
uses: actions/upload-pages-artifact@v4
|
|
75
|
+
with:
|
|
76
|
+
path: './afterpython/_website/build'
|
|
77
|
+
|
|
78
|
+
deploy:
|
|
79
|
+
environment:
|
|
80
|
+
name: github-pages
|
|
81
|
+
# This will dynamically generate the deployment URL
|
|
82
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
needs: build
|
|
85
|
+
steps:
|
|
86
|
+
- name: Deploy to GitHub Pages
|
|
87
|
+
id: deployment
|
|
88
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
default_install_hook_types:
|
|
2
|
+
- pre-commit
|
|
3
|
+
- commit-msg
|
|
4
|
+
- pre-push
|
|
5
|
+
exclude: ^afterpython/_website/
|
|
6
|
+
repos:
|
|
7
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
8
|
+
rev: v6.0.0
|
|
9
|
+
hooks:
|
|
10
|
+
- id: trailing-whitespace
|
|
11
|
+
stages:
|
|
12
|
+
- pre-commit
|
|
13
|
+
- id: end-of-file-fixer
|
|
14
|
+
stages:
|
|
15
|
+
- pre-commit
|
|
16
|
+
- id: check-yaml
|
|
17
|
+
stages:
|
|
18
|
+
- pre-commit
|
|
19
|
+
- id: check-toml
|
|
20
|
+
stages:
|
|
21
|
+
- pre-commit
|
|
22
|
+
- id: check-added-large-files
|
|
23
|
+
stages:
|
|
24
|
+
- pre-commit
|
|
25
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
26
|
+
rev: v0.14.8
|
|
27
|
+
hooks:
|
|
28
|
+
- id: ruff-check
|
|
29
|
+
stages:
|
|
30
|
+
- pre-commit
|
|
31
|
+
args:
|
|
32
|
+
- --config
|
|
33
|
+
- ./afterpython/ruff.toml
|
|
34
|
+
- id: ruff-format
|
|
35
|
+
stages:
|
|
36
|
+
- pre-commit
|
|
37
|
+
args:
|
|
38
|
+
- --config
|
|
39
|
+
- ./afterpython/ruff.toml
|
|
40
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
41
|
+
rev: v4.10.0
|
|
42
|
+
hooks:
|
|
43
|
+
- id: commitizen
|
|
44
|
+
stages:
|
|
45
|
+
- commit-msg
|
|
46
|
+
- id: commitizen-branch
|
|
47
|
+
stages:
|
|
48
|
+
- pre-push
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release to PyPI and GitHub
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*' # Trigger on all version tags (v0.1.0, v0.1.0rc1, v0.1.0.dev4, etc.)
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write # Required for creating GitHub releases
|
|
13
|
+
id-token: write # Required for PyPI trusted publishing
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: '3.11'
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v7
|
|
26
|
+
with:
|
|
27
|
+
version: "latest"
|
|
28
|
+
|
|
29
|
+
- name: Build package
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Publish to PyPI
|
|
33
|
+
run: uv publish
|
|
34
|
+
|
|
35
|
+
- name: Determine if pre-release
|
|
36
|
+
id: check_prerelease
|
|
37
|
+
run: |
|
|
38
|
+
# Extract version from tag (remove 'v' prefix)
|
|
39
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
40
|
+
|
|
41
|
+
# Check if this is a pre-release or dev version
|
|
42
|
+
# Matches: dev, rc, alpha, beta, a1, b2 (but not 'stable-version')
|
|
43
|
+
if [[ "$VERSION" =~ (dev|rc|alpha|beta|a[0-9]+|b[0-9]+) ]]; then
|
|
44
|
+
echo "prerelease=true" >> $GITHUB_OUTPUT
|
|
45
|
+
else
|
|
46
|
+
echo "prerelease=false" >> $GITHUB_OUTPUT
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub Release
|
|
50
|
+
uses: softprops/action-gh-release@v2
|
|
51
|
+
with:
|
|
52
|
+
tag_name: ${{ github.ref_name }}
|
|
53
|
+
name: ${{ github.ref_name }}
|
|
54
|
+
generate_release_notes: false
|
|
55
|
+
prerelease: ${{ steps.check_prerelease.outputs.prerelease }}
|
|
56
|
+
files: dist/* # Attach built packages (wheel + sdist)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Ruff configuration
|
|
2
|
+
# See More: https://docs.astral.sh/ruff/configuration/
|
|
3
|
+
|
|
4
|
+
line-length = 88
|
|
5
|
+
target-version = "py311"
|
|
6
|
+
|
|
7
|
+
[lint]
|
|
8
|
+
# ruff default rules:
|
|
9
|
+
select = [
|
|
10
|
+
"E4", # import-related errors (e.g., undefined names in imports)
|
|
11
|
+
"E7", # statement errors (e.g., multiple statements on one line)
|
|
12
|
+
"E9", # runtime errors (e.g., syntax errors, indentation issues)
|
|
13
|
+
"F", # Pyflakes - logical errors (e.g., unused imports, undefined variables)
|
|
14
|
+
]
|
|
15
|
+
# ignored rules:
|
|
16
|
+
ignore = [
|
|
17
|
+
"E501", # line too long
|
|
18
|
+
]
|
|
19
|
+
# extended rules:
|
|
20
|
+
extend-select = [
|
|
21
|
+
"I", # isort - import sorting
|
|
22
|
+
"UP", # pyupgrade - upgrade syntax for newer Python
|
|
23
|
+
"B", # flake8-bugbear - find likely bugs
|
|
24
|
+
"SIM", # flake8-simplify - simplify code
|
|
25
|
+
# "C90", # mccabe - complexity checking
|
|
26
|
+
"N", # pep8-naming - enforce naming conventions
|
|
27
|
+
"RUF", # ruff-specific rules
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[format]
|
|
31
|
+
quote-style = "double" # double quotes for strings
|
|
32
|
+
indent-style = "space" # indent with spaces, rather than tabs.
|