afterpython 0.1.0__py3-none-any.whl → 0.1.0.dev4__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/{templates/pre-commit-config-template.yaml → .pre-commit-config.yaml} +1 -1
- afterpython/_io/toml.py +3 -5
- afterpython/_paths.py +1 -1
- afterpython/_typing.py +2 -1
- afterpython/builders/__init__.py +3 -12
- afterpython/builders/metadata.py +3 -7
- afterpython/builders/molab.py +99 -0
- afterpython/cli/__init__.py +1 -0
- afterpython/cli/commands/build.py +63 -136
- afterpython/cli/commands/bump.py +0 -30
- afterpython/cli/commands/check.py +1 -1
- afterpython/cli/commands/clean.py +3 -4
- afterpython/cli/commands/commit.py +3 -21
- afterpython/cli/commands/commitizen.py +1 -1
- afterpython/cli/commands/dev.py +13 -80
- afterpython/cli/commands/format.py +1 -1
- afterpython/cli/commands/init.py +39 -48
- afterpython/cli/commands/install.py +2 -8
- afterpython/cli/commands/pre_commit.py +1 -1
- afterpython/cli/commands/preview.py +1 -1
- afterpython/cli/commands/start.py +4 -8
- afterpython/cli/commands/sync.py +21 -65
- afterpython/cli/commands/update.py +8 -31
- afterpython/cli/main.py +14 -75
- afterpython/const.py +2 -1
- afterpython/cz.toml +27 -0
- afterpython/pcu.py +1 -3
- afterpython/templates/deploy-workflow-template.yml +5 -22
- afterpython/templates/release-workflow-template.yml +3 -3
- afterpython/templates/ruff-template.toml +1 -1
- afterpython/tools/_afterpython.py +10 -73
- afterpython/tools/_git.py +2 -28
- afterpython/tools/commitizen.py +45 -5
- afterpython/tools/github_actions.py +53 -23
- afterpython/tools/myst.py +38 -82
- afterpython/tools/pre_commit.py +31 -6
- afterpython/tools/pyproject.py +2 -40
- afterpython/utils.py +3 -87
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev4.dist-info}/METADATA +13 -39
- afterpython-0.1.0.dev4.dist-info/RECORD +47 -0
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev4.dist-info}/WHEEL +1 -1
- afterpython/builders/content_json.py +0 -131
- afterpython/builders/index_md.py +0 -94
- afterpython/builders/jupyter_notebook.py +0 -293
- afterpython/builders/markdown.py +0 -271
- afterpython/cli/commands/add.py +0 -67
- afterpython/cli/commands/init_branch_rules.py +0 -9
- afterpython/cli/commands/lock.py +0 -23
- afterpython/cli/commands/remove.py +0 -67
- afterpython/templates/ci-workflow-template.yml +0 -197
- afterpython/templates/cz-template.toml +0 -7
- afterpython/templates/dependabot-template.yml +0 -30
- afterpython/tools/branch_rules.py +0 -106
- afterpython-0.1.0.dist-info/RECORD +0 -57
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev4.dist-info}/entry_points.txt +0 -0
afterpython/builders/markdown.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
|
|
5
|
-
import afterpython as ap
|
|
6
|
-
from afterpython.const import CONTENT_TYPES
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _read_markdown(markdown_path: Path, content_path: Path) -> str | None:
|
|
10
|
-
"""Read and validate a markdown file.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
markdown_path: Absolute path to the markdown file
|
|
14
|
-
content_path: Path relative to afterpython/ for logging
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
Markdown content as a string if valid, None if invalid or error occurred
|
|
18
|
-
"""
|
|
19
|
-
try:
|
|
20
|
-
with open(markdown_path, encoding="utf-8") as f:
|
|
21
|
-
content = f.read()
|
|
22
|
-
return content
|
|
23
|
-
except FileNotFoundError:
|
|
24
|
-
click.echo(f"✗ Skipping {content_path} - file not found")
|
|
25
|
-
return None
|
|
26
|
-
except Exception as e:
|
|
27
|
-
click.echo(f"✗ Error reading {content_path}: {e}")
|
|
28
|
-
return None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _write_markdown(content: str, markdown_path: Path):
|
|
32
|
-
"""Write markdown content to file.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
content: The markdown content to write
|
|
36
|
-
markdown_path: Absolute path to the markdown file
|
|
37
|
-
"""
|
|
38
|
-
with open(markdown_path, "w", encoding="utf-8") as f:
|
|
39
|
-
f.write(content)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _has_frontmatter(content: str) -> bool:
|
|
43
|
-
"""Check if markdown content has frontmatter.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
content: The markdown file content as a string
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
True if the content starts with ---, False otherwise
|
|
50
|
-
"""
|
|
51
|
-
return content.strip().startswith("---")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _read_frontmatter(content: str) -> str | None:
|
|
55
|
-
"""Read frontmatter content from markdown.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
content: The markdown file content as a string
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Frontmatter content as a string (including --- delimiters) if it exists, None otherwise
|
|
62
|
-
"""
|
|
63
|
-
if not _has_frontmatter(content):
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
lines = content.split("\n")
|
|
67
|
-
|
|
68
|
-
# Find the closing --- of frontmatter
|
|
69
|
-
closing_index = -1
|
|
70
|
-
for i in range(1, len(lines)):
|
|
71
|
-
if lines[i].strip() == "---":
|
|
72
|
-
closing_index = i
|
|
73
|
-
break
|
|
74
|
-
|
|
75
|
-
if closing_index == -1:
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
|
-
# Return frontmatter including both --- delimiters
|
|
79
|
-
return "\n".join(lines[: closing_index + 1])
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _write_frontmatter(content: str, frontmatter: str) -> str:
|
|
83
|
-
"""Write frontmatter content to markdown.
|
|
84
|
-
|
|
85
|
-
Low-level function that handles frontmatter replacement.
|
|
86
|
-
If frontmatter exists in content, replaces it. If not, prepends it.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
content: The markdown file content as a string
|
|
90
|
-
frontmatter: The complete frontmatter content as a string (should include --- delimiters)
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
The updated markdown content with the new frontmatter
|
|
94
|
-
"""
|
|
95
|
-
if _has_frontmatter(content):
|
|
96
|
-
# Replace existing frontmatter
|
|
97
|
-
lines = content.split("\n")
|
|
98
|
-
|
|
99
|
-
# Find the closing --- of existing frontmatter
|
|
100
|
-
closing_index = -1
|
|
101
|
-
for i in range(1, len(lines)):
|
|
102
|
-
if lines[i].strip() == "---":
|
|
103
|
-
closing_index = i
|
|
104
|
-
break
|
|
105
|
-
|
|
106
|
-
if closing_index > -1:
|
|
107
|
-
# Remove old frontmatter and prepend new one
|
|
108
|
-
body = "\n".join(lines[closing_index + 1 :])
|
|
109
|
-
return frontmatter + "\n" + body
|
|
110
|
-
else:
|
|
111
|
-
# Malformed frontmatter, just prepend new frontmatter
|
|
112
|
-
return frontmatter + "\n" + content
|
|
113
|
-
else:
|
|
114
|
-
# No frontmatter exists, prepend it
|
|
115
|
-
return frontmatter + "\n" + content
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _ensure_blank_line_after_frontmatter(content: str) -> str:
|
|
119
|
-
"""Ensure there's a blank line between frontmatter and content.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
content: The markdown content
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
Content with blank line after frontmatter if frontmatter exists
|
|
126
|
-
"""
|
|
127
|
-
if not _has_frontmatter(content):
|
|
128
|
-
return content
|
|
129
|
-
|
|
130
|
-
lines = content.split("\n")
|
|
131
|
-
|
|
132
|
-
# Find the closing --- of frontmatter
|
|
133
|
-
closing_index = -1
|
|
134
|
-
for i in range(1, len(lines)):
|
|
135
|
-
if lines[i].strip() == "---":
|
|
136
|
-
closing_index = i
|
|
137
|
-
break
|
|
138
|
-
|
|
139
|
-
if closing_index == -1:
|
|
140
|
-
return content
|
|
141
|
-
|
|
142
|
-
# Check if there's already a blank line after the closing ---, and if the next line is not empty, insert a blank line
|
|
143
|
-
if closing_index + 1 < len(lines) and lines[closing_index + 1].strip() != "":
|
|
144
|
-
lines.insert(closing_index + 1, "")
|
|
145
|
-
|
|
146
|
-
return "\n".join(lines)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _add_fields_to_frontmatter(content: str, fields: dict[str, str]) -> str:
|
|
150
|
-
"""Add or update fields in frontmatter.
|
|
151
|
-
|
|
152
|
-
High-level function that handles frontmatter field manipulation.
|
|
153
|
-
If frontmatter exists, adds new fields to it. If not, creates new frontmatter with those fields.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
content: The markdown file content as a string
|
|
157
|
-
fields: A dict of fields to add/update (e.g., {"thumbnail": "null"})
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
The updated markdown content with the new fields in frontmatter
|
|
161
|
-
"""
|
|
162
|
-
existing_frontmatter = _read_frontmatter(content)
|
|
163
|
-
|
|
164
|
-
if existing_frontmatter:
|
|
165
|
-
# Parse existing frontmatter and add new fields
|
|
166
|
-
lines = existing_frontmatter.split("\n")
|
|
167
|
-
|
|
168
|
-
# Find the closing --- of frontmatter
|
|
169
|
-
closing_index = -1
|
|
170
|
-
for i in range(1, len(lines)):
|
|
171
|
-
if lines[i].strip() == "---":
|
|
172
|
-
closing_index = i
|
|
173
|
-
break
|
|
174
|
-
|
|
175
|
-
if closing_index > 0:
|
|
176
|
-
# Insert new fields before the closing ---
|
|
177
|
-
for key, value in fields.items():
|
|
178
|
-
# Check if field already exists
|
|
179
|
-
if not any(line.strip().startswith(f"{key}:") for line in lines):
|
|
180
|
-
lines.insert(closing_index, f"{key}: {value}")
|
|
181
|
-
closing_index += 1 # Adjust index for next insertion
|
|
182
|
-
|
|
183
|
-
new_frontmatter = "\n".join(lines)
|
|
184
|
-
return _write_frontmatter(content, new_frontmatter)
|
|
185
|
-
else:
|
|
186
|
-
# Create new frontmatter with the provided fields
|
|
187
|
-
field_lines = [f"{key}: {value}" for key, value in fields.items()]
|
|
188
|
-
new_frontmatter = "---\n" + "\n".join(field_lines) + "\n---"
|
|
189
|
-
return _write_frontmatter(content, new_frontmatter)
|
|
190
|
-
|
|
191
|
-
return content
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _add_thumbnail_to_markdown(
|
|
195
|
-
content: str, thumbnail: str, content_path: Path
|
|
196
|
-
) -> tuple[str, bool]:
|
|
197
|
-
"""Add thumbnail field to a markdown file's frontmatter.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
content: The markdown content
|
|
201
|
-
thumbnail: The thumbnail value to add
|
|
202
|
-
content_path: Path for logging purposes
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
Tuple of (modified_content, was_modified)
|
|
206
|
-
"""
|
|
207
|
-
# Check if thumbnail is already set in frontmatter
|
|
208
|
-
frontmatter_content = _read_frontmatter(content)
|
|
209
|
-
if frontmatter_content and "thumbnail:" in frontmatter_content:
|
|
210
|
-
return content, False
|
|
211
|
-
|
|
212
|
-
# Add thumbnail using the fields parameter
|
|
213
|
-
modified_content = _add_fields_to_frontmatter(
|
|
214
|
-
content, fields={"thumbnail": thumbnail}
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
if frontmatter_content:
|
|
218
|
-
click.echo(
|
|
219
|
-
f" ↳ Added 'thumbnail: {thumbnail}' to frontmatter in: {content_path}"
|
|
220
|
-
)
|
|
221
|
-
else:
|
|
222
|
-
click.echo(
|
|
223
|
-
f" ↳ Created frontmatter with 'thumbnail: {thumbnail}' in: {content_path}"
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
return modified_content, True
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def build_markdown():
|
|
230
|
-
"""Build markdown files for all content types"""
|
|
231
|
-
from afterpython.tools._afterpython import get_default_thumbnail
|
|
232
|
-
|
|
233
|
-
for content_type in CONTENT_TYPES:
|
|
234
|
-
# Skip doc content type - docs don't need default thumbnails
|
|
235
|
-
if content_type == "doc":
|
|
236
|
-
continue
|
|
237
|
-
|
|
238
|
-
click.echo(f"Building markdown files for {content_type}/...")
|
|
239
|
-
|
|
240
|
-
path = ap.paths.afterpython_path / content_type.lower()
|
|
241
|
-
|
|
242
|
-
# Iterate over all .md files
|
|
243
|
-
for markdown_path in path.rglob("*.md"):
|
|
244
|
-
# Skip files in _build directory
|
|
245
|
-
if "_build" in markdown_path.parts:
|
|
246
|
-
continue
|
|
247
|
-
|
|
248
|
-
# Get path relative to afterpython/
|
|
249
|
-
content_path = markdown_path.relative_to(ap.paths.afterpython_path)
|
|
250
|
-
|
|
251
|
-
# Get default thumbnail for this content type
|
|
252
|
-
default_thumbnail = get_default_thumbnail(content_type, markdown_path)
|
|
253
|
-
|
|
254
|
-
# Read the markdown file
|
|
255
|
-
content = _read_markdown(markdown_path, content_path)
|
|
256
|
-
if content is None:
|
|
257
|
-
continue
|
|
258
|
-
|
|
259
|
-
# Ensure blank line after frontmatter (fixes existing files)
|
|
260
|
-
original_content = content
|
|
261
|
-
content = _ensure_blank_line_after_frontmatter(content)
|
|
262
|
-
blank_line_added = content != original_content
|
|
263
|
-
|
|
264
|
-
# Add thumbnail to frontmatter
|
|
265
|
-
modified_content, was_modified = _add_thumbnail_to_markdown(
|
|
266
|
-
content, default_thumbnail, content_path
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
# Write back if modified (either blank line or thumbnail)
|
|
270
|
-
if was_modified or blank_line_added:
|
|
271
|
-
_write_markdown(modified_content, markdown_path)
|
afterpython/cli/commands/add.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
from click.exceptions import Exit
|
|
5
|
-
|
|
6
|
-
from afterpython.utils import has_pixi, has_uv
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@click.command()
|
|
10
|
-
@click.option(
|
|
11
|
-
"--optional",
|
|
12
|
-
type=str,
|
|
13
|
-
default=None,
|
|
14
|
-
required=False,
|
|
15
|
-
help="Add to an optional dependency group (pixi: mapped to 'optional' feature)",
|
|
16
|
-
)
|
|
17
|
-
@click.option(
|
|
18
|
-
"--group",
|
|
19
|
-
type=str,
|
|
20
|
-
default=None,
|
|
21
|
-
required=False,
|
|
22
|
-
help="Add to a dependency group (pixi: mapped to same-named feature)",
|
|
23
|
-
)
|
|
24
|
-
@click.argument("lib", type=str, required=True)
|
|
25
|
-
def add(optional: str | None, group: str | None, lib: str):
|
|
26
|
-
"""Add a new dependency to the project (manages both uv and pixi if present)"""
|
|
27
|
-
if not has_uv():
|
|
28
|
-
click.echo("uv not found. Please install uv first.")
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
if optional and group:
|
|
32
|
-
click.echo("Error: Cannot specify both --optional and --group")
|
|
33
|
-
raise Exit(1)
|
|
34
|
-
|
|
35
|
-
result = subprocess.run(
|
|
36
|
-
[
|
|
37
|
-
"uv",
|
|
38
|
-
"add",
|
|
39
|
-
*(["--optional", optional] if optional else []),
|
|
40
|
-
*(["--group", group] if group else []),
|
|
41
|
-
lib,
|
|
42
|
-
],
|
|
43
|
-
check=False,
|
|
44
|
-
)
|
|
45
|
-
if result.returncode != 0:
|
|
46
|
-
raise Exit(result.returncode)
|
|
47
|
-
|
|
48
|
-
if has_pixi():
|
|
49
|
-
# NOTE: pixi doesn't support --optional, so we need to use --feature instead
|
|
50
|
-
if optional:
|
|
51
|
-
feature_name = "optional"
|
|
52
|
-
elif group:
|
|
53
|
-
feature_name = group
|
|
54
|
-
else:
|
|
55
|
-
feature_name = None
|
|
56
|
-
result = subprocess.run(
|
|
57
|
-
[
|
|
58
|
-
"pixi",
|
|
59
|
-
"add",
|
|
60
|
-
"--pypi",
|
|
61
|
-
*(["--feature", feature_name] if feature_name else []),
|
|
62
|
-
lib,
|
|
63
|
-
],
|
|
64
|
-
check=False,
|
|
65
|
-
)
|
|
66
|
-
if result.returncode != 0:
|
|
67
|
-
raise Exit(result.returncode)
|
afterpython/cli/commands/lock.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
from click.exceptions import Exit
|
|
5
|
-
|
|
6
|
-
from afterpython.utils import has_pixi, has_uv
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@click.command()
|
|
10
|
-
def lock():
|
|
11
|
-
"""Lock project dependencies (manages both uv and pixi if present)"""
|
|
12
|
-
if not has_uv():
|
|
13
|
-
click.echo("uv not found. Please install uv first.")
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
result = subprocess.run(["uv", "lock"], check=False)
|
|
17
|
-
if result.returncode != 0:
|
|
18
|
-
raise Exit(result.returncode)
|
|
19
|
-
|
|
20
|
-
if has_pixi():
|
|
21
|
-
result = subprocess.run(["pixi", "lock"], check=False)
|
|
22
|
-
if result.returncode != 0:
|
|
23
|
-
raise Exit(result.returncode)
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
from click.exceptions import Exit
|
|
5
|
-
|
|
6
|
-
from afterpython.utils import has_pixi, has_uv
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@click.command()
|
|
10
|
-
@click.option(
|
|
11
|
-
"--optional",
|
|
12
|
-
type=str,
|
|
13
|
-
default=None,
|
|
14
|
-
required=False,
|
|
15
|
-
help="Remove from an optional dependency group (pixi: mapped to 'optional' feature)",
|
|
16
|
-
)
|
|
17
|
-
@click.option(
|
|
18
|
-
"--group",
|
|
19
|
-
type=str,
|
|
20
|
-
default=None,
|
|
21
|
-
required=False,
|
|
22
|
-
help="Remove from a dependency group (pixi: mapped to same-named feature)",
|
|
23
|
-
)
|
|
24
|
-
@click.argument("lib", type=str, required=True)
|
|
25
|
-
def remove(optional: str | None, group: str | None, lib: str):
|
|
26
|
-
"""Remove a dependency from the project (manages both uv and pixi if present)"""
|
|
27
|
-
if not has_uv():
|
|
28
|
-
click.echo("uv not found. Please install uv first.")
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
if optional and group:
|
|
32
|
-
click.echo("Error: Cannot specify both --optional and --group")
|
|
33
|
-
raise Exit(1)
|
|
34
|
-
|
|
35
|
-
result = subprocess.run(
|
|
36
|
-
[
|
|
37
|
-
"uv",
|
|
38
|
-
"remove",
|
|
39
|
-
*(["--optional", optional] if optional else []),
|
|
40
|
-
*(["--group", group] if group else []),
|
|
41
|
-
lib,
|
|
42
|
-
],
|
|
43
|
-
check=False,
|
|
44
|
-
)
|
|
45
|
-
if result.returncode != 0:
|
|
46
|
-
raise Exit(result.returncode)
|
|
47
|
-
|
|
48
|
-
if has_pixi():
|
|
49
|
-
# NOTE: pixi doesn't support --optional, so we need to use --feature instead
|
|
50
|
-
if optional:
|
|
51
|
-
feature_name = "optional"
|
|
52
|
-
elif group:
|
|
53
|
-
feature_name = group
|
|
54
|
-
else:
|
|
55
|
-
feature_name = None
|
|
56
|
-
result = subprocess.run(
|
|
57
|
-
[
|
|
58
|
-
"pixi",
|
|
59
|
-
"remove",
|
|
60
|
-
"--pypi",
|
|
61
|
-
*(["--feature", feature_name] if feature_name else []),
|
|
62
|
-
lib,
|
|
63
|
-
],
|
|
64
|
-
check=False,
|
|
65
|
-
)
|
|
66
|
-
if result.returncode != 0:
|
|
67
|
-
raise Exit(result.returncode)
|
|
@@ -1,197 +0,0 @@
|
|
|
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:
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|