Skip to content

Commit e47768a

Browse files
committed
improve tasks file
1 parent 73a9c0c commit e47768a

File tree

2 files changed

+281
-24
lines changed

2 files changed

+281
-24
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,3 @@ dist/
5050
# Temporary
5151
*.log
5252
*.tmp
53-
54-
55-
tasks.py
56-
TODO.md

tasks.py

Lines changed: 281 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,310 @@
11
from invoke.tasks import task
2+
from invoke import Context, task
3+
from typing import Any
24
import shlex
35

6+
from invoke import Context, task
7+
from typing import Any
8+
9+
410
@task
5-
def test(c):
6-
c.run("poetry run pytest --cov=gen_surv --cov-report=term --cov-report=xml")
11+
def test(c: Context) -> None:
12+
"""
13+
Run pytest via Poetry with coverage reporting for the 'gen_surv' package.
14+
15+
This task will:
16+
1. Execute 'pytest' through Poetry.
17+
2. Generate a terminal coverage report.
18+
3. Write an XML coverage report to 'coverage.xml'.
19+
20+
:param c: Invoke context used to run shell commands.
21+
:raises TypeError: If 'c' is not an Invoke Context.
22+
"""
23+
# Ensure we were passed a valid Context object.
24+
if not isinstance(c, Context):
25+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r} instead")
26+
27+
# Build the command string. You can adjust '--cov=gen_surv' if you
28+
# need to cover a different package or add extra pytest flags.
29+
command = (
30+
"poetry run pytest "
31+
"--cov=gen_surv "
32+
"--cov-report=term "
33+
"--cov-report=xml"
34+
)
35+
36+
# Run pytest.
37+
# - warn=True: capture non-zero exit codes without aborting Invoke.
38+
# - pty=False: pytest doesn’t require an interactive TTY here.
39+
result = c.run(command, warn=True, pty=False)
40+
41+
# Check the exit code and report accordingly.
42+
if result.ok:
43+
print("✔️ All tests passed.")
44+
else:
45+
print("❌ Some tests failed.")
46+
print(f"Exit code: {result.exited}")
47+
if result.stderr:
48+
print("Error output:")
49+
print(result.stderr)
50+
751

852
@task
9-
def docs(c):
10-
c.run("poetry run sphinx-build docs/source docs/build")
53+
def docs(c: Context) -> None:
54+
"""
55+
Build Sphinx documentation for the project using Poetry.
56+
57+
This task will:
58+
1. Run 'sphinx-build' via Poetry.
59+
2. Read source files from 'docs/source'.
60+
3. Output HTML (or other format) into 'docs/build'.
61+
62+
:param c: Invoke context, used to run shell commands.
63+
:type c: Context
64+
:raises TypeError: If 'c' is not an Invoke Context.
65+
"""
66+
# Verify we have a proper Invoke Context.
67+
if not isinstance(c, Context):
68+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r}")
69+
70+
# Construct the Sphinx build command. Adjust paths if needed.
71+
command = "poetry run sphinx-build docs/source docs/build"
72+
73+
# Execute sphinx-build.
74+
# - warn=True: capture non-zero exits without immediately aborting Invoke.
75+
# - pty=False: sphinx-build does not require interactive input.
76+
result = c.run(command, warn=True, pty=False)
77+
78+
# Report on the result of the documentation build.
79+
if result.ok:
80+
print("✔️ Documentation built successfully.")
81+
else:
82+
print("❌ Documentation build failed.")
83+
print(f"Exit code: {result.exited}")
84+
if result.stderr:
85+
print("Error output:")
86+
print(result.stderr)
87+
88+
from invoke import Context, task
89+
from typing import Any
90+
1191

1292
@task
13-
def stubs(c):
14-
c.run("poetry run stubgen -p gen_surv -o stubs")
93+
def stubs(c: Context) -> None:
94+
"""
95+
Generate type stubs for the 'gen_surv' package using stubgen and Poetry.
96+
97+
This task will:
98+
1. Run 'stubgen' via Poetry to analyze 'gen_surv'.
99+
2. Output the generated stubs into the 'stubs' directory.
100+
101+
:param c: Invoke context used to run shell commands.
102+
:raises TypeError: If 'c' is not an Invoke Context.
103+
"""
104+
# Verify that 'c' is the correct Invoke Context.
105+
if not isinstance(c, Context):
106+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r}")
107+
108+
# Build the stubgen command. Adjust '-p gen_surv' or output path if needed.
109+
command = "poetry run stubgen -p gen_surv -o stubs"
110+
111+
# Execute stubgen.
112+
# - warn=True: capture non-zero exit codes without aborting Invoke.
113+
# - pty=False: stubgen does not require interactive input.
114+
result = c.run(command, warn=True, pty=False)
115+
116+
# Report on the outcome of stub generation.
117+
if result.ok:
118+
print("✔️ Type stubs generated successfully in 'stubs/'.")
119+
else:
120+
print("❌ Stub generation failed.")
121+
print(f"Exit code: {result.exited}")
122+
if result.stderr:
123+
print("Error output:")
124+
print(result.stderr)
125+
15126

16127
@task
17-
def build(c):
18-
c.run("poetry build")
128+
def build(c: Context) -> None:
129+
"""
130+
Build the project distributions using Poetry.
131+
132+
This task will:
133+
1. Run 'poetry build' to create source and wheel packages.
134+
2. Place the built artifacts in the 'dist/' directory.
135+
136+
:param c: Invoke context used to run shell commands.
137+
:raises TypeError: If 'c' is not an Invoke Context.
138+
"""
139+
# Verify that we received a valid Invoke Context.
140+
if not isinstance(c, Context):
141+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r}")
142+
143+
# Construct the build command. Adjust if you need custom build options.
144+
command = "poetry build"
145+
146+
# Execute the build.
147+
# - warn=True: capture non-zero exit codes without aborting Invoke.
148+
# - pty=False: no interactive input is required for building.
149+
result = c.run(command, warn=True, pty=False)
150+
151+
# Report the result of the build process.
152+
if result.ok:
153+
print("✔️ Build completed successfully. Artifacts are in the 'dist/' directory.")
154+
else:
155+
print("❌ Build failed.")
156+
print(f"Exit code: {result.exited}")
157+
if result.stderr:
158+
print("Error output:")
159+
print(result.stderr)
19160

20161
@task
21-
def publish(c):
22-
c.run("poetry publish --build")
162+
def publish(c: Context) -> None:
163+
"""
164+
Build and publish the package to PyPI using Poetry.
165+
166+
This task will:
167+
1. Build the distribution via 'poetry publish --build'.
168+
2. Attach to a pseudo-TTY so you can enter credentials or confirm prompts.
169+
3. Not abort immediately if an error occurs; instead, it will print diagnostics.
170+
171+
:param c: Invoke context, used to run shell commands.
172+
:type c: Context
173+
"""
174+
# Run the poetry publish command.
175+
# - warn=True: do not abort on non-zero exit, so we can inspect and report.
176+
# - pty=True: allocate a pseudo-TTY for interactive prompts (username/password, etc.).
177+
result = c.run(
178+
"poetry publish --build",
179+
warn=True,
180+
pty=True,
181+
)
182+
183+
# If the exit code is zero, the publish succeeded.
184+
if result.ok:
185+
print("✔️ Package published successfully.")
186+
return
187+
188+
# Otherwise, print out details to help debug.
189+
print("❌ Poetry publish failed.")
190+
print(f"Exit code: {result.exited}")
191+
if result.stderr:
192+
print("Error output:")
193+
print(result.stderr)
194+
else:
195+
print("No stderr output captured.")
23196

24197
@task
25-
def clean(c):
26-
c.run("rm -rf dist build docs/build .pytest_cache .mypy_cache coverage.xml .coverage stubs")
198+
def clean(c: Context) -> None:
199+
"""
200+
Remove build artifacts, caches, and generated files.
201+
202+
This task will:
203+
1. Delete the 'dist' and 'build' directories.
204+
2. Remove generated documentation in 'docs/build'.
205+
3. Clear pytest and mypy caches.
206+
4. Delete coverage reports and stub files.
207+
208+
:param c: Invoke context used to run shell commands.
209+
:raises TypeError: If 'c' is not an Invoke Context.
210+
"""
211+
# Verify the argument is an Invoke Context.
212+
if not isinstance(c, Context):
213+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r}")
214+
215+
# List of paths and files to remove. Adjust if you add new artifacts.
216+
targets = [
217+
"dist",
218+
"build",
219+
"docs/build",
220+
".pytest_cache",
221+
".mypy_cache",
222+
"coverage.xml",
223+
".coverage",
224+
"stubs",
225+
]
226+
227+
# Join targets into a single rm command.
228+
# Using '-rf' to force removal without prompts.
229+
command = f"rm -rf {' '.join(targets)}"
230+
231+
# Execute the cleanup command.
232+
# - warn=True: capture non-zero exits without aborting Invoke.
233+
# - pty=False: no interactive input is required.
234+
result = c.run(command, warn=True, pty=False)
235+
236+
# Report the outcome of the cleanup.
237+
if result.ok:
238+
print("✔️ Cleaned all build artifacts and caches.")
239+
else:
240+
print("❌ Cleanup failed for some targets.")
241+
print(f"Exit code: {result.exited}")
242+
if result.stderr:
243+
print("Error output:")
244+
print(result.stderr)
27245

28246
@task
29-
def git_push(c):
247+
def gitpush(c: Context) -> None:
30248
"""
31-
Stage all changes, prompt for a commit message, create a signed commit, and push.
249+
Stage all changes, prompt for a commit message, create a signed commit, and push to the remote repository.
250+
251+
This task will:
252+
1. Verify that 'c' is an Invoke Context.
253+
2. Run 'git add .' to stage all unstaged changes.
254+
3. Prompt the user for a commit message; abort if empty.
255+
4. Sanitize the message, then run 'git commit -S -m <message>'.
256+
5. Run 'git push' to publish commits.
257+
258+
:param c: Invoke Context used to run shell commands.
259+
:raises TypeError: If 'c' is not an Invoke Context.
32260
"""
33-
import getpass
261+
# Verify the argument is a valid Invoke Context.
262+
if not isinstance(c, Context):
263+
raise TypeError(f"Expected Invoke Context, got {type(c).__name__!r}")
34264

35-
c.run("git add .")
265+
# Stage all changes.
266+
result_add = c.run("git add .", warn=True, pty=False)
267+
if not result_add.ok:
268+
print("❌ Failed to stage changes (git add).")
269+
print(f"Exit code: {result_add.exited}")
270+
if result_add.stderr:
271+
print("Error output:")
272+
print(result_add.stderr)
273+
return
36274

37275
try:
38-
# Prompt for a commit message
276+
# Prompt for a commit message.
39277
message = input("Enter commit message: ").strip()
40278
if not message:
41279
print("Aborting: empty commit message.")
42280
return
43281

282+
# Sanitize the message to prevent shell injection.
44283
sanitized_message = shlex.quote(message)
45-
c.run(f"git commit -S -m {sanitized_message}")
46-
c.run("git push")
284+
285+
# Create a signed commit. Use a pseudo-TTY so GPG passphrase can be entered if needed.
286+
result_commit = c.run(
287+
f"git commit -S -m {sanitized_message}",
288+
warn=True,
289+
pty=True,
290+
)
291+
if not result_commit.ok:
292+
print("❌ Commit failed.")
293+
print(f"Exit code: {result_commit.exited}")
294+
if result_commit.stderr:
295+
print("Error output:")
296+
print(result_commit.stderr)
297+
return
298+
299+
# Push to the remote repository.
300+
result_push = c.run("git push", warn=True, pty=False)
301+
if result_push.ok:
302+
print("✔️ Changes pushed successfully.")
303+
else:
304+
print("❌ Push failed.")
305+
print(f"Exit code: {result_push.exited}")
306+
if result_push.stderr:
307+
print("Error output:")
308+
print(result_push.stderr)
47309
except KeyboardInterrupt:
48310
print("\nAborted by user.")
49-

0 commit comments

Comments
 (0)