hip-cargo 0.0.2__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.
- hip_cargo-0.0.2/PKG-INFO +672 -0
- hip_cargo-0.0.2/README.md +644 -0
- hip_cargo-0.0.2/pyproject.toml +81 -0
- hip_cargo-0.0.2/src/hip_cargo/__init__.py +6 -0
- hip_cargo-0.0.2/src/hip_cargo/cabs/__init__.py +27 -0
- hip_cargo-0.0.2/src/hip_cargo/cabs/generate_cab.yaml +22 -0
- hip_cargo-0.0.2/src/hip_cargo/cabs/generate_function.yaml +18 -0
- hip_cargo-0.0.2/src/hip_cargo/cli/__init__.py +25 -0
- hip_cargo-0.0.2/src/hip_cargo/cli/generate_cab.py +56 -0
- hip_cargo-0.0.2/src/hip_cargo/cli/generate_function.py +53 -0
- hip_cargo-0.0.2/src/hip_cargo/core/__init__.py +1 -0
- hip_cargo-0.0.2/src/hip_cargo/core/generate_cab.py +46 -0
- hip_cargo-0.0.2/src/hip_cargo/core/generate_function.py +26 -0
- hip_cargo-0.0.2/src/hip_cargo/py.typed +0 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/__init__.py +1 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/cab_to_function.py +593 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/callbacks.py +92 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/decorators.py +66 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/introspector.py +336 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/signature_utils.py +27 -0
- hip_cargo-0.0.2/src/hip_cargo/utils/yaml_generator.py +67 -0
hip_cargo-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: hip-cargo
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Tools for generating Stimela cab definitions from Python functions
|
|
5
|
+
Keywords: stimela,typer,cli,yaml,code-generation,radio-astronomy
|
|
6
|
+
Author: landmanbester
|
|
7
|
+
Author-email: landmanbester <lbester@sarao.ac.za>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
20
|
+
Requires-Dist: typer>=0.12.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: Bug Tracker, https://github.com/landmanbester/hip-cargo/issues
|
|
25
|
+
Project-URL: Homepage, https://github.com/landmanbester/hip-cargo
|
|
26
|
+
Project-URL: Repository, https://github.com/landmanbester/hip-cargo
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# hip-cargo
|
|
30
|
+
|
|
31
|
+
A guide to designing auto-documenting CLI interfaces using Typer + conversion utilities.
|
|
32
|
+
If you are creating a new package the instructions below will guide you on how to structure it.
|
|
33
|
+
The `generate-function` utility is available to assist in converting an existing package to the `hip-cargo` format but there will be some manual steps involved.
|
|
34
|
+
The philosophy behind this design is to allow having a lightweight version of the package that only installs the bits required to generate `--help` from the CLI and the cab definitions that can then be used with `stimela`.
|
|
35
|
+
The full package should be available as a container image that can be used with `stimela`.
|
|
36
|
+
The image should be tagged with the package version so that `stimela` will automatically pull the image that matches the cab configuration.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install hip-cargo
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or for development:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/landmanbester/hip-cargo.git
|
|
48
|
+
cd hip-cargo
|
|
49
|
+
uv sync
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### 1. Decorate your Python CLI
|
|
55
|
+
|
|
56
|
+
Something like the following goes in `src/mypackage/cli/process.py`
|
|
57
|
+
```python
|
|
58
|
+
import typer
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
from typing import NewType
|
|
61
|
+
from typing_extensions import Annotated
|
|
62
|
+
from hip_cargo import stimela_cab, stimela_output
|
|
63
|
+
|
|
64
|
+
# custom types (stimela has e.g. File, URI, MS and Directory)
|
|
65
|
+
File = NewType("File", Path)
|
|
66
|
+
URI = NewType("URI", Path)
|
|
67
|
+
MS = NewType("MS", Path)
|
|
68
|
+
Directory = NewType("Directory", Path)
|
|
69
|
+
|
|
70
|
+
@stimela_cab(
|
|
71
|
+
name="my_processor",
|
|
72
|
+
info="Process data files",
|
|
73
|
+
)
|
|
74
|
+
@stimela_output(
|
|
75
|
+
name="output_file",
|
|
76
|
+
dtype="File",
|
|
77
|
+
info="{input_file}.processed",
|
|
78
|
+
required=True,
|
|
79
|
+
)
|
|
80
|
+
def process(
|
|
81
|
+
input_ms: Annotated[MS, typer.Argument(parser=MS, help="Input MS to process")], # note the parser=MS bit. This is required for non-standard types
|
|
82
|
+
output_dir: Annotated[Directory, typer.Option(parser=Directory, help="Output Directory for results")] = Path("./output"),
|
|
83
|
+
threshold: Annotated[float, typer.Option(help="Threshold value")] = 0.5,
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Process a data file.
|
|
87
|
+
"""
|
|
88
|
+
# All your manual parameter wrangling here
|
|
89
|
+
from mypackage.core.process import process as process_core
|
|
90
|
+
return process_core(*args, **kwargs)
|
|
91
|
+
```
|
|
92
|
+
Note that `*args` and `**kwargs` need to passed explicitly.
|
|
93
|
+
Then register the command in the `src/mypackage/cli/__init__.py` with something like the following
|
|
94
|
+
```python
|
|
95
|
+
"""Lightweight CLI for mypackage."""
|
|
96
|
+
|
|
97
|
+
import typer
|
|
98
|
+
|
|
99
|
+
app = typer.Typer(
|
|
100
|
+
name="mypackage",
|
|
101
|
+
help="Scientific computing package",
|
|
102
|
+
no_args_is_help=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Register commands
|
|
106
|
+
from mypackage.cli.process import process
|
|
107
|
+
|
|
108
|
+
app.command(name="process")(process)
|
|
109
|
+
|
|
110
|
+
__all__ = ["app"]
|
|
111
|
+
```
|
|
112
|
+
That's it, if you have something like the following
|
|
113
|
+
```toml
|
|
114
|
+
[project.scripts]
|
|
115
|
+
mypackage = "mypackage.cli:app"
|
|
116
|
+
```
|
|
117
|
+
in your `pyproject.toml` you should now be able to run
|
|
118
|
+
```bash
|
|
119
|
+
app --help
|
|
120
|
+
```
|
|
121
|
+
and
|
|
122
|
+
```bash
|
|
123
|
+
app process --help
|
|
124
|
+
```
|
|
125
|
+
from the command line and have a beautifully formatted CLI for your package.
|
|
126
|
+
Note that you can register multiple commands under `app`.
|
|
127
|
+
|
|
128
|
+
### 2. Generate the Stimela cab definition
|
|
129
|
+
|
|
130
|
+
If you have the CLI definition you can convert it to a can using e.g.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cargo generate-cab mypackage.process src/mypackage/cabs/process.yaml
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This should be automated using `scrips/generate_cabs.py`, but the above command is useful for testing.
|
|
137
|
+
|
|
138
|
+
### 3. Generate Python function from existing cab (reverse)
|
|
139
|
+
|
|
140
|
+
If you are converting an existing package to the `hip-cargo` format there is a utility function available viz.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cargo generate-function /path/to/existing_cab.yaml -o myfunction.py
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Currently, this won't add things like `rich_output_panel`, but it should help to get you started.
|
|
147
|
+
The program should recognize custom types and add the
|
|
148
|
+
```
|
|
149
|
+
from pathlib import Path
|
|
150
|
+
from typing import NewType
|
|
151
|
+
|
|
152
|
+
MS = NewType("MS", Path)
|
|
153
|
+
```
|
|
154
|
+
bit for you. It should also add the `parser=MS` in the `typer.Option()` bit for you.
|
|
155
|
+
|
|
156
|
+
## Project Structure for hip-cargo Packages
|
|
157
|
+
|
|
158
|
+
Packages following the hip-cargo pattern should be structured to enable both lightweight cab definitions and full execution environments:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
my-scientific-package/
|
|
162
|
+
├── src/
|
|
163
|
+
│ └── mypackage/
|
|
164
|
+
│ ├── __init__.py
|
|
165
|
+
│ ├── utils/ # Utilities used by core algorithms
|
|
166
|
+
│ │ ├── __init__.py
|
|
167
|
+
│ │ └── operator.py
|
|
168
|
+
│ ├── core/ # Core implementations with standard python type hints (no Annotated or custom types)
|
|
169
|
+
│ │ ├── __init__.py
|
|
170
|
+
│ │ ├── process.py
|
|
171
|
+
│ │ └── analyze.py
|
|
172
|
+
│ ├── cli/ # Lightweight CLI layer
|
|
173
|
+
│ │ ├── __init__.py # Main Typer app
|
|
174
|
+
│ │ ├── process.py # Individual commands
|
|
175
|
+
│ │ └── analyze.py
|
|
176
|
+
│ └── cabs/ # Generated cab definitions (inside mypackage)
|
|
177
|
+
│ ├── __init__.py
|
|
178
|
+
│ ├── process.yaml
|
|
179
|
+
│ └── analyze.yaml
|
|
180
|
+
├── scripts/
|
|
181
|
+
│ └── generate_cabs.py # Automation script
|
|
182
|
+
├── Dockerfile # For containerization
|
|
183
|
+
├── pyproject.toml
|
|
184
|
+
└── README.md
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Key Principles
|
|
188
|
+
|
|
189
|
+
1. **Separate CLI from implementation**: Keep CLI modules lightweight with lazy imports. Keep them all in the `src/mypackage/cli` directory and define the CLI for each command in a separate file. Construct the main Typer app in `src/mypackage/cli/__init__.py` and register commands there.
|
|
190
|
+
2. **Separate cabs directory at same level as `cli`**: Use `hip-cargo` to auto-generate cabs into in `src/mypackage/cabs/` directory with the `generate_cabs.py` script. There should be a separate file for each cab.
|
|
191
|
+
3. **Single app, multiple commands**: Use one Typer app that registers all commands. If you need a separate app you might as well create a separate repository for it.
|
|
192
|
+
4. **Lazy imports**: Import heavy dependencies (NumPy, JAX, Dask) only when executing
|
|
193
|
+
5. **Linked GitHub package with container image**: Maintain an up to date `Dockerfile` that installs the full package and use **Docker** (or **Podman**) to upload the image to the GitHub Container registry. Link this to your GitHub repository.
|
|
194
|
+
|
|
195
|
+
### Example Structure
|
|
196
|
+
|
|
197
|
+
**`src/mypackage/cli/__init__.py`:**
|
|
198
|
+
```python
|
|
199
|
+
"""Lightweight CLI for mypackage."""
|
|
200
|
+
|
|
201
|
+
import typer
|
|
202
|
+
|
|
203
|
+
app = typer.Typer(
|
|
204
|
+
name="mypackage",
|
|
205
|
+
help="Scientific computing package",
|
|
206
|
+
no_args_is_help=True,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Register commands
|
|
210
|
+
from mypackage.cli.process import process
|
|
211
|
+
from mypackage.cli.analyze import analyze
|
|
212
|
+
|
|
213
|
+
app.command(name="process")(process)
|
|
214
|
+
app.command(name="analyze")(analyze)
|
|
215
|
+
|
|
216
|
+
__all__ = ["app"]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**`src/mypackage/cli/process.py`:**
|
|
220
|
+
```python
|
|
221
|
+
"""Process command - lightweight wrapper."""
|
|
222
|
+
|
|
223
|
+
from pathlib import Path
|
|
224
|
+
from typing import NewType
|
|
225
|
+
from typing_extensions import Annotated
|
|
226
|
+
import typer
|
|
227
|
+
from hip_cargo import stimela_cab, stimela_output
|
|
228
|
+
|
|
229
|
+
MS = NewType("MS", Path)
|
|
230
|
+
|
|
231
|
+
@stimela_cab(name="mypackage_process", info="Process data")
|
|
232
|
+
@stimela_output(name="output", dtype="File", info="{input_file}.out")
|
|
233
|
+
def process(
|
|
234
|
+
input_ms: Annotated[MS, typer.Argument(parser=MS, help="Input File")],
|
|
235
|
+
param: Annotated[float, typer.Option(help="Parameter")] = 1.0,
|
|
236
|
+
):
|
|
237
|
+
"""Process data files."""
|
|
238
|
+
# Lazy import - only loaded when executing
|
|
239
|
+
from mypackage.operators.core_algorithm import process_data
|
|
240
|
+
|
|
241
|
+
return process_data(input_file, param)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**`pyproject.toml`:**
|
|
245
|
+
```toml
|
|
246
|
+
[project]
|
|
247
|
+
name = "mypackage"
|
|
248
|
+
dependencies = [
|
|
249
|
+
"typer>=0.12.0",
|
|
250
|
+
"hip-cargo>=0.1.0",
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
[project.optional-dependencies]
|
|
254
|
+
# Full scientific stack
|
|
255
|
+
full = [
|
|
256
|
+
"numpy>=1.24.0",
|
|
257
|
+
"jax>=0.4.0",
|
|
258
|
+
# ... heavy dependencies
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
[project.scripts]
|
|
262
|
+
mypackage = "mypackage.cli:app"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**`scripts/generate_cabs.py`:**
|
|
266
|
+
```python
|
|
267
|
+
"""Generate all cab definitions."""
|
|
268
|
+
import subprocess
|
|
269
|
+
from pathlib import Path
|
|
270
|
+
|
|
271
|
+
CLI_MODULES = [
|
|
272
|
+
"mypackage.cli.process",
|
|
273
|
+
"mypackage.cli.analyze",
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
CABS_DIR = Path("src/mypackage/cabs")
|
|
277
|
+
CABS_DIR.mkdir(exist_ok=True)
|
|
278
|
+
|
|
279
|
+
for module in CLI_MODULES:
|
|
280
|
+
cmd_name = module.split(".")[-1]
|
|
281
|
+
output = CABS_DIR / f"{cmd_name}.yaml"
|
|
282
|
+
|
|
283
|
+
print(f"Generating {output}...")
|
|
284
|
+
subprocess.run([
|
|
285
|
+
"cargo", "generate-cab",
|
|
286
|
+
module,
|
|
287
|
+
str(output)
|
|
288
|
+
], check=True)
|
|
289
|
+
|
|
290
|
+
print("✓ All cabs generated")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Installation Modes
|
|
294
|
+
|
|
295
|
+
Users can install your package in different ways:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# Lightweight (just CLI and cab definitions)
|
|
299
|
+
pip install mypackage
|
|
300
|
+
|
|
301
|
+
# Full (with all scientific dependencies)
|
|
302
|
+
pip install mypackage[full]
|
|
303
|
+
|
|
304
|
+
# Development
|
|
305
|
+
pip install -e "mypackage[full,dev]"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Integration with cult-cargo
|
|
309
|
+
|
|
310
|
+
For integration with Stimela's cult-cargo:
|
|
311
|
+
|
|
312
|
+
1. **Make cabs discoverable:**
|
|
313
|
+
```python
|
|
314
|
+
# src/mypackage/cabs/__init__.py
|
|
315
|
+
from pathlib import Path
|
|
316
|
+
|
|
317
|
+
CAB_DIR = Path(__file__).parent
|
|
318
|
+
AVAILABLE_CABS = [p.stem for p in CAB_DIR.glob("*.yml")]
|
|
319
|
+
|
|
320
|
+
def get_cab_path(name: str) -> Path:
|
|
321
|
+
"""Get path to a cab definition."""
|
|
322
|
+
return CAB_DIR / f"{name}.yml"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
2. **cult-cargo imports lightweight version:**
|
|
326
|
+
|
|
327
|
+
We have to decide whether we want to add this kind of thing to `cult-cargo`:
|
|
328
|
+
|
|
329
|
+
```toml
|
|
330
|
+
# In cult-cargo's pyproject.toml
|
|
331
|
+
[tool.poetry.dependencies]
|
|
332
|
+
mypackage = "^1.0.0" # Not mypackage[full]
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
However, it should be possible to just
|
|
336
|
+
```bash
|
|
337
|
+
uv pip install mypackage==x.x.x
|
|
338
|
+
```
|
|
339
|
+
without any dependency conflicts. If not we have to think about ephemeral virtual environments.
|
|
340
|
+
|
|
341
|
+
3. **Users run with Stimela:**
|
|
342
|
+
```bash
|
|
343
|
+
# Native: requires full installation
|
|
344
|
+
pip install mypackage[full]
|
|
345
|
+
stimela run recipe.yml
|
|
346
|
+
|
|
347
|
+
# Singularity: uses container (lightweight install sufficient)
|
|
348
|
+
pip install mypackage
|
|
349
|
+
stimela run recipe.yml -S
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Container Images and GitHub Actions
|
|
353
|
+
|
|
354
|
+
For Stimela to use your package in containerized environments, you should publish OCI container images to GitHub Container Registry (ghcr.io). This section shows how to automate this with GitHub Actions.
|
|
355
|
+
|
|
356
|
+
### 1. Create a Dockerfile
|
|
357
|
+
|
|
358
|
+
Add a `Dockerfile` at the root of your repository:
|
|
359
|
+
|
|
360
|
+
```dockerfile
|
|
361
|
+
FROM python:3.11-slim
|
|
362
|
+
|
|
363
|
+
WORKDIR /app
|
|
364
|
+
|
|
365
|
+
# Install uv for fast package installation
|
|
366
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
367
|
+
|
|
368
|
+
# Copy package files
|
|
369
|
+
COPY pyproject.toml README.md ./
|
|
370
|
+
COPY src/ src/
|
|
371
|
+
|
|
372
|
+
# Install package with full dependencies using uv (much faster than pip)
|
|
373
|
+
RUN uv pip install --system --no-cache .
|
|
374
|
+
|
|
375
|
+
# Make CLI available
|
|
376
|
+
ENTRYPOINT ["mypackage"]
|
|
377
|
+
CMD ["--help"]
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 2. Set up GitHub Actions Workflow
|
|
381
|
+
|
|
382
|
+
Create `.github/workflows/publish-container.yml`:
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
name: Build and Publish Container
|
|
386
|
+
|
|
387
|
+
on:
|
|
388
|
+
push:
|
|
389
|
+
tags:
|
|
390
|
+
- 'v*.*.*' # Trigger on version tags (e.g., v1.0.0)
|
|
391
|
+
workflow_dispatch: # Allow manual triggering
|
|
392
|
+
|
|
393
|
+
env:
|
|
394
|
+
REGISTRY: ghcr.io
|
|
395
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
396
|
+
|
|
397
|
+
jobs:
|
|
398
|
+
build-and-push:
|
|
399
|
+
runs-on: ubuntu-latest
|
|
400
|
+
permissions:
|
|
401
|
+
contents: read
|
|
402
|
+
packages: write
|
|
403
|
+
|
|
404
|
+
steps:
|
|
405
|
+
- name: Checkout repository
|
|
406
|
+
uses: actions/checkout@v5
|
|
407
|
+
|
|
408
|
+
- name: Log in to Container Registry
|
|
409
|
+
uses: docker/login-action@v3
|
|
410
|
+
with:
|
|
411
|
+
registry: ${{ env.REGISTRY }}
|
|
412
|
+
username: ${{ github.actor }}
|
|
413
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
414
|
+
|
|
415
|
+
- name: Extract metadata (tags, labels)
|
|
416
|
+
id: meta
|
|
417
|
+
uses: docker/metadata-action@v5
|
|
418
|
+
with:
|
|
419
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
420
|
+
tags: |
|
|
421
|
+
type=semver,pattern={{version}}
|
|
422
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
423
|
+
type=semver,pattern={{major}}
|
|
424
|
+
type=sha,prefix={{branch}}-
|
|
425
|
+
|
|
426
|
+
- name: Build and push Docker image
|
|
427
|
+
uses: docker/build-push-action@v5
|
|
428
|
+
with:
|
|
429
|
+
context: .
|
|
430
|
+
push: true
|
|
431
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
432
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### 3. Link Container to GitHub Package
|
|
436
|
+
|
|
437
|
+
To associate the container image with your repository:
|
|
438
|
+
|
|
439
|
+
1. **Automatic linking**: If your workflow pushes to `ghcr.io/username/repository-name`, GitHub automatically creates a package linked to the repository.
|
|
440
|
+
|
|
441
|
+
2. **Manual linking** (if needed):
|
|
442
|
+
- Go to your repository on GitHub
|
|
443
|
+
- Navigate to the "Packages" section
|
|
444
|
+
- Click on your container package
|
|
445
|
+
- Click "Connect repository" in the sidebar
|
|
446
|
+
- Select your repository from the dropdown
|
|
447
|
+
|
|
448
|
+
3. **Set package visibility**:
|
|
449
|
+
- In the package settings, set visibility to "Public" for open-source projects
|
|
450
|
+
- This allows Stimela to pull images without authentication
|
|
451
|
+
|
|
452
|
+
### 4. Version Tagging Best Practices
|
|
453
|
+
|
|
454
|
+
The workflow above creates multiple tags for each release:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# For release v1.2.3, creates:
|
|
458
|
+
ghcr.io/username/mypackage:1.2.3 # Full version
|
|
459
|
+
ghcr.io/username/mypackage:1.2 # Minor version
|
|
460
|
+
ghcr.io/username/mypackage:1 # Major version
|
|
461
|
+
ghcr.io/username/mypackage:main-sha123456 # Branch + commit SHA
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
This allows users to pin to specific versions or track latest minor/major releases.
|
|
465
|
+
|
|
466
|
+
### 5. Triggering a Build
|
|
467
|
+
|
|
468
|
+
**Automated (recommended):**
|
|
469
|
+
```bash
|
|
470
|
+
# Create and push a version tag
|
|
471
|
+
git tag v1.0.0
|
|
472
|
+
git push origin v1.0.0
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
The GitHub Action will automatically build and publish the container.
|
|
476
|
+
|
|
477
|
+
**Manual:**
|
|
478
|
+
- Go to "Actions" tab in GitHub
|
|
479
|
+
- Select "Build and Publish Container"
|
|
480
|
+
- Click "Run workflow"
|
|
481
|
+
|
|
482
|
+
### 6. Using the Container with Stimela
|
|
483
|
+
|
|
484
|
+
Once published, users can reference your container in Stimela recipes:
|
|
485
|
+
|
|
486
|
+
```yaml
|
|
487
|
+
cabs:
|
|
488
|
+
- name: mypackage
|
|
489
|
+
image: ghcr.io/username/mypackage:1.0.0
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Stimela will automatically pull the matching version based on the cab configuration.
|
|
493
|
+
|
|
494
|
+
### 7. Local Testing
|
|
495
|
+
|
|
496
|
+
Test your container locally before pushing:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
# Build
|
|
500
|
+
docker build -t mypackage:test .
|
|
501
|
+
|
|
502
|
+
# Run
|
|
503
|
+
docker run --rm mypackage:test --help
|
|
504
|
+
docker run --rm mypackage:test process --help
|
|
505
|
+
|
|
506
|
+
# Test with mounted data
|
|
507
|
+
docker run --rm -v $(pwd)/data:/data mypackage:test process /data/input.ms
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Type Inference
|
|
511
|
+
|
|
512
|
+
`hip-cargo` automatically recognizes custom `stimela` types. The `generate-cab` command should add
|
|
513
|
+
```python
|
|
514
|
+
from pathlib import Path
|
|
515
|
+
from typing import NewType
|
|
516
|
+
|
|
517
|
+
MS = NewType("MS", Path)
|
|
518
|
+
Directory = NewType("Directory", Path)
|
|
519
|
+
URI = NewType("URI", Path)
|
|
520
|
+
File = NewType("File", Path)
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
to the preamble of functions generated from cabs that use these types.
|
|
524
|
+
It should also add the `parser` bit to the type hint Annotation e.g. for the custom `MS` dtype we need
|
|
525
|
+
```
|
|
526
|
+
def process(input_ms: Annotated[MS, typer.Option(parser=MS)]):
|
|
527
|
+
pass
|
|
528
|
+
```
|
|
529
|
+
One quirk of this approach is that parameters which have `None` as the default need to be defined as e.g.
|
|
530
|
+
```
|
|
531
|
+
def process(input_ms: Annotated[MS | None, typer.Option(parser=MS)]) = None:
|
|
532
|
+
pass
|
|
533
|
+
```
|
|
534
|
+
Python then parses this as `Optional[MS]` which is just an alias for `Union[MS | None]`. This should be handled correctly such that the `generate-cab` command places `dtype: MS` in the cab definition and the `generate-function` command correctly generates the function signature above. These custom types are currently limited to only two possible types in the `Union` and should be specified using the newer `dtype1 | dtype2` format in the function definition (one of which may be `None`). All standard python types should just work.
|
|
535
|
+
|
|
536
|
+
## Decorators
|
|
537
|
+
|
|
538
|
+
### `@stimela_cab`
|
|
539
|
+
|
|
540
|
+
Marks a function as a Stimela cab.
|
|
541
|
+
|
|
542
|
+
- `name`: Cab name
|
|
543
|
+
- `info`: Description
|
|
544
|
+
- `policies`: Optional dict of cab-level policies
|
|
545
|
+
|
|
546
|
+
### `@stimela_output`
|
|
547
|
+
|
|
548
|
+
Defines a `stimela` output. When defining functions from cabs the `generate-function` command should check for the following parameter fields
|
|
549
|
+
|
|
550
|
+
- `name`: Output name (top level, one below `cabs`)
|
|
551
|
+
- `dtype`: Data type (File, Directory, MS, etc.)
|
|
552
|
+
- `info`: Help string
|
|
553
|
+
- `required`: Whether output is required (default: False)
|
|
554
|
+
- `implicit`: If implicit is `True` the parameter should not be placed in the function definition. If implicit is `False` (the default), the parameter needs to be added to the function signature.
|
|
555
|
+
|
|
556
|
+
## Features
|
|
557
|
+
|
|
558
|
+
- ✅ Automatic type inference from Python type hints
|
|
559
|
+
- ✅ Support for Typer Arguments (positional) and Options
|
|
560
|
+
- ✅ Multiple outputs automatically added to function signature if they are not implicit
|
|
561
|
+
- ✅ List types with automatic `repeat: list` policy
|
|
562
|
+
- ✅ Proper handling of default values and required parameters
|
|
563
|
+
|
|
564
|
+
## Development
|
|
565
|
+
|
|
566
|
+
This project uses:
|
|
567
|
+
- [uv](https://github.com/astral-sh/uv) for dependency management
|
|
568
|
+
- [ruff](https://github.com/astral-sh/ruff) for linting and formatting
|
|
569
|
+
- [typer](https://typer.tiangolo.com/) for the CLI
|
|
570
|
+
|
|
571
|
+
### Setting Up Development Environment
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
# Clone the repository
|
|
575
|
+
git clone https://github.com/landmanbester/hip-cargo.git
|
|
576
|
+
cd hip-cargo
|
|
577
|
+
|
|
578
|
+
# Install dependencies with development tools
|
|
579
|
+
uv sync --group dev
|
|
580
|
+
|
|
581
|
+
# Install pre-commit hooks (recommended)
|
|
582
|
+
uv run pre-commit install
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Pre-commit Hooks
|
|
586
|
+
|
|
587
|
+
This project uses [pre-commit](https://pre-commit.com/) to automatically check code quality before commits. The hooks run:
|
|
588
|
+
|
|
589
|
+
- **ruff linting**: Checks code style and catches common errors
|
|
590
|
+
- **ruff formatting**: Ensures consistent code formatting
|
|
591
|
+
- **trailing whitespace**: Removes trailing whitespace
|
|
592
|
+
- **end-of-file-fixer**: Ensures files end with a newline
|
|
593
|
+
- **check-yaml**: Validates YAML syntax
|
|
594
|
+
- **check-toml**: Validates TOML syntax
|
|
595
|
+
- **check-merge-conflict**: Prevents committing merge conflict markers
|
|
596
|
+
- **check-added-large-files**: Prevents accidentally committing large files
|
|
597
|
+
|
|
598
|
+
#### Installing Pre-commit Hooks
|
|
599
|
+
|
|
600
|
+
After cloning the repository, install the pre-commit hooks:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
uv run pre-commit install
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
This will automatically run the hooks before each commit. If any checks fail, the commit will be blocked until you fix the issues.
|
|
607
|
+
|
|
608
|
+
#### Running Hooks Manually
|
|
609
|
+
|
|
610
|
+
You can run the hooks manually on all files:
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
# Run on all files
|
|
614
|
+
uv run pre-commit run --all-files
|
|
615
|
+
|
|
616
|
+
# Run on staged files only
|
|
617
|
+
uv run pre-commit run
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
#### Updating Hook Versions
|
|
621
|
+
|
|
622
|
+
To update hook versions to the latest:
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
uv run pre-commit autoupdate
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Manual Code Quality Checks
|
|
629
|
+
|
|
630
|
+
If you prefer to run checks manually without pre-commit:
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
# Format code
|
|
634
|
+
uv run ruff format .
|
|
635
|
+
|
|
636
|
+
# Check and auto-fix linting issues
|
|
637
|
+
uv run ruff check . --fix
|
|
638
|
+
|
|
639
|
+
# Run tests
|
|
640
|
+
uv run pytest -v
|
|
641
|
+
|
|
642
|
+
# Run tests with coverage
|
|
643
|
+
uv run pytest --cov=hip_cargo --cov-report=term-missing
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Contributing Workflow
|
|
647
|
+
|
|
648
|
+
1. **Create a feature branch**:
|
|
649
|
+
```bash
|
|
650
|
+
git checkout -b feature/your-feature-name
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
2. **Make your changes** and ensure tests pass:
|
|
654
|
+
```bash
|
|
655
|
+
uv run pytest -v
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
3. **Format and lint** (automatically done by pre-commit):
|
|
659
|
+
```bash
|
|
660
|
+
git add .
|
|
661
|
+
git commit -m "feat: your feature description"
|
|
662
|
+
# Pre-commit hooks run automatically
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
4. **Push and create a pull request**:
|
|
666
|
+
```bash
|
|
667
|
+
git push origin feature/your-feature-name
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## License
|
|
671
|
+
|
|
672
|
+
MIT License
|