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.
Files changed (66) hide show
  1. afterpython/__init__.py +13 -0
  2. afterpython/_io/toml.py +105 -0
  3. afterpython/_io/yaml.py +26 -0
  4. afterpython/_paths.py +39 -0
  5. afterpython/_typing.py +4 -0
  6. afterpython/builders/__init__.py +17 -0
  7. afterpython/builders/content_json.py +131 -0
  8. afterpython/builders/index_md.py +94 -0
  9. afterpython/builders/jupyter_notebook.py +293 -0
  10. afterpython/builders/markdown.py +271 -0
  11. afterpython/builders/metadata.py +50 -0
  12. afterpython/cli/__init__.py +3 -0
  13. afterpython/cli/commands/add.py +67 -0
  14. afterpython/cli/commands/build.py +260 -0
  15. afterpython/cli/commands/bump.py +118 -0
  16. afterpython/cli/commands/check.py +36 -0
  17. afterpython/cli/commands/clean.py +85 -0
  18. afterpython/cli/commands/commit.py +52 -0
  19. afterpython/cli/commands/commitizen.py +32 -0
  20. afterpython/cli/commands/dev.py +213 -0
  21. afterpython/cli/commands/format.py +28 -0
  22. afterpython/cli/commands/init.py +107 -0
  23. afterpython/cli/commands/init_branch_rules.py +9 -0
  24. afterpython/cli/commands/install.py +28 -0
  25. afterpython/cli/commands/lock.py +23 -0
  26. afterpython/cli/commands/pre_commit.py +66 -0
  27. afterpython/cli/commands/preview.py +22 -0
  28. afterpython/cli/commands/release.py +67 -0
  29. afterpython/cli/commands/remove.py +67 -0
  30. afterpython/cli/commands/start.py +144 -0
  31. afterpython/cli/commands/sync.py +152 -0
  32. afterpython/cli/commands/update.py +176 -0
  33. afterpython/cli/main.py +132 -0
  34. afterpython/const.py +4 -0
  35. afterpython/main.py +25 -0
  36. afterpython/pcu.py +166 -0
  37. afterpython/templates/ci-workflow-template.yml +197 -0
  38. afterpython/templates/cz-template.toml +7 -0
  39. afterpython/templates/dependabot-template.yml +30 -0
  40. afterpython/templates/deploy-workflow-template.yml +88 -0
  41. afterpython/templates/pre-commit-config-template.yaml +48 -0
  42. afterpython/templates/release-workflow-template.yml +56 -0
  43. afterpython/templates/ruff-template.toml +32 -0
  44. afterpython/tools/_afterpython.py +128 -0
  45. afterpython/tools/_git.py +137 -0
  46. afterpython/tools/branch_rules.py +106 -0
  47. afterpython/tools/commitizen.py +13 -0
  48. afterpython/tools/github_actions.py +48 -0
  49. afterpython/tools/myst.py +208 -0
  50. afterpython/tools/pre_commit.py +37 -0
  51. afterpython/tools/pyproject.py +110 -0
  52. afterpython/utils.py +299 -0
  53. afterpython/version.py +39 -0
  54. afterpython-0.1.0.dist-info/METADATA +131 -0
  55. afterpython-0.1.0.dist-info/RECORD +57 -0
  56. afterpython-0.1.0.dist-info/WHEEL +4 -0
  57. afterpython-0.1.0.dist-info/entry_points.txt +4 -0
  58. afterpython/README.py +0 -158
  59. afterpython/images/favicon.ico +0 -0
  60. afterpython/images/logo-trace.svg +0 -3
  61. afterpython/images/logo.png +0 -0
  62. afterpython/images/logo.svg +0 -2
  63. afterpython-0.0.1.dev2.dist-info/LICENSE +0 -201
  64. afterpython-0.0.1.dev2.dist-info/METADATA +0 -20
  65. afterpython-0.0.1.dev2.dist-info/RECORD +0 -9
  66. 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,7 @@
1
+ [tool.commitizen]
2
+ name = "cz_conventional_commits"
3
+ tag_format = "v$version"
4
+ version_scheme = "pep440"
5
+ version_provider = "uv"
6
+ update_changelog_on_bump = false
7
+ major_version_zero = true
@@ -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.