gri-multitrack 0.1.0__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.
- gri_multitrack-0.1.0/.docs_other_projects.md +85 -0
- gri_multitrack-0.1.0/.githooks/pre-push +18 -0
- gri_multitrack-0.1.0/.gitignore +36 -0
- gri_multitrack-0.1.0/.gitlab-ci-deps.yml +18 -0
- gri_multitrack-0.1.0/.gitlab-ci.yml +99 -0
- gri_multitrack-0.1.0/.hatch_build.py +46 -0
- gri_multitrack-0.1.0/.pre-commit-config.yaml +47 -0
- gri_multitrack-0.1.0/.pre-commit-unskipped.py +38 -0
- gri_multitrack-0.1.0/.python-version +1 -0
- gri_multitrack-0.1.0/.ruff.toml +52 -0
- gri_multitrack-0.1.0/.vscode/settings.json +15 -0
- gri_multitrack-0.1.0/CONTRIBUTING.md +124 -0
- gri_multitrack-0.1.0/CONVOLVE.md +555 -0
- gri_multitrack-0.1.0/LICENSE +21 -0
- gri_multitrack-0.1.0/MULTITRACK.md +344 -0
- gri_multitrack-0.1.0/PKG-INFO +146 -0
- gri_multitrack-0.1.0/PLAN.md +282 -0
- gri_multitrack-0.1.0/README.md +113 -0
- gri_multitrack-0.1.0/examples/two_target_demo.py +83 -0
- gri_multitrack-0.1.0/gri_multitrack/__init__.py +100 -0
- gri_multitrack-0.1.0/gri_multitrack/association.py +196 -0
- gri_multitrack-0.1.0/gri_multitrack/cardinality.py +68 -0
- gri_multitrack-0.1.0/gri_multitrack/ingest.py +232 -0
- gri_multitrack-0.1.0/gri_multitrack/lifecycle.py +262 -0
- gri_multitrack-0.1.0/gri_multitrack/mfa.py +436 -0
- gri_multitrack-0.1.0/gri_multitrack/output.py +255 -0
- gri_multitrack-0.1.0/gri_multitrack/scoring.py +165 -0
- gri_multitrack-0.1.0/gri_multitrack/track.py +303 -0
- gri_multitrack-0.1.0/gri_multitrack/tracker.py +186 -0
- gri_multitrack-0.1.0/pyproject.toml +103 -0
- gri_multitrack-0.1.0/report.xml +1 -0
- gri_multitrack-0.1.0/test/__init__.py +0 -0
- gri_multitrack-0.1.0/test/_scene.py +113 -0
- gri_multitrack-0.1.0/test/test_association.py +59 -0
- gri_multitrack-0.1.0/test/test_cardinality.py +43 -0
- gri_multitrack-0.1.0/test/test_ingest.py +92 -0
- gri_multitrack-0.1.0/test/test_lifecycle.py +84 -0
- gri_multitrack-0.1.0/test/test_mfa.py +104 -0
- gri_multitrack-0.1.0/test/test_scoring.py +53 -0
- gri_multitrack-0.1.0/test/test_track.py +72 -0
- gri_multitrack-0.1.0/test/test_tracker.py +155 -0
- gri_multitrack-0.1.0/uv.lock +1786 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[](https://geosolresearch.com)
|
|
2
|
+
|
|
3
|
+
# GeoSol FOSS Projects
|
|
4
|
+
|
|
5
|
+
[Back to main README](README.md)
|
|
6
|
+
|
|
7
|
+
GeoSol Research is working to create, maintain, and improve several FOSS projects in the geolocation space. Here is what we are currently offering!
|
|
8
|
+
|
|
9
|
+
## Layer 1: Foundation
|
|
10
|
+
|
|
11
|
+
### [GRI Fitter](https://gitlab.com/geosol-foss/python/gri-fitter)
|
|
12
|
+
|
|
13
|
+
Equation fitting wrapper around scipy.optimize.curve\_fit. Simplifies curve fitting workflows with a clean API.
|
|
14
|
+
|
|
15
|
+
### [GRI Memoize](https://gitlab.com/geosol-foss/python/gri-memoize)
|
|
16
|
+
|
|
17
|
+
Class-based memoization with full type hints and IDE integration. Provides caching decorators for performance optimization.
|
|
18
|
+
|
|
19
|
+
### [GRI Signal](https://gitlab.com/geosol-foss/python/gri-signal)
|
|
20
|
+
|
|
21
|
+
Functional signal processing library with pure functions operating on numpy arrays. Includes signal generation (tones, PRN, pulsed), channel simulation (delay, AWGN, Doppler, multipath), modulation (AM/FM/PM, BPSK/QPSK/QAM), sampling (ADC), filtering (lowpass, highpass, bandpass, notch, analytic), spectral analysis (PSD, SNR, THD, time-bandwidth product), cross-correlation, and beamforming.
|
|
22
|
+
|
|
23
|
+
### [GRI Utils](https://gitlab.com/geosol-foss/python/gri-utils)
|
|
24
|
+
|
|
25
|
+
The math back end to many other projects. This includes constants, coordinate conversion and transformation, orbit propagation, observable calculations, and geolocation algorithms. Most are based around numpy arrays.
|
|
26
|
+
|
|
27
|
+
## Layer 2: Mid-Level
|
|
28
|
+
|
|
29
|
+
### [GRI Ell](https://gitlab.com/geosol-foss/python/gri-ell)
|
|
30
|
+
|
|
31
|
+
An object to handle statistical locations like ellipse and ellipsoid, centered around a GRI-Position object. Transformations between statistical spaces, conversions between sigma and percentages in 2D and 3D, statistical distances, and attributes.
|
|
32
|
+
|
|
33
|
+
### [GRI Iono](https://gitlab.com/geosol-foss/python/gri-iono)
|
|
34
|
+
|
|
35
|
+
Ionospheric delay corrections using Bent, IRI, and NeQuick models.
|
|
36
|
+
|
|
37
|
+
### [GRI Iono Data](https://gitlab.com/geosol-foss/python/gri-iono-data)
|
|
38
|
+
|
|
39
|
+
Solar flux and NeQuickG coefficient data pipeline for ionospheric modeling.
|
|
40
|
+
|
|
41
|
+
### [GRI NSEpoch](https://gitlab.com/geosol-foss/python/gri-nsepoch)
|
|
42
|
+
|
|
43
|
+
A lot of the work we do is down to nanosecond or tens of nanosecond accuracy. We needed an object that could both handle that level of precision as well as do a lot of the string <=> time conversions for the many semi-standards formats we run into in this field.
|
|
44
|
+
|
|
45
|
+
### [GRI Obs](https://gitlab.com/geosol-foss/python/gri-obs)
|
|
46
|
+
|
|
47
|
+
Typed observation objects (TDOA, FDOA, AOA, Range, PDOA) for geolocation measurements. Each observation carries measurement data, noise characterization, sensor geometry, and measurement math (predicted values, Jacobians, noise covariance) for EKF integration with Kalman filters.
|
|
48
|
+
|
|
49
|
+
### [GRI Plot](https://gitlab.com/geosol-foss/python/gri-plot)
|
|
50
|
+
|
|
51
|
+
Plotting utilities for Plotly and Matplotlib. Provides consistent styling and common plot types for geospatial visualization.
|
|
52
|
+
|
|
53
|
+
### [GRI Pos](https://gitlab.com/geosol-foss/python/gri-pos)
|
|
54
|
+
|
|
55
|
+
A position object that greatly simplifies handling coordinates, distances, and memory, mostly on the WGS-84 ellipsoid.
|
|
56
|
+
|
|
57
|
+
### [GRI Terrain](https://gitlab.com/geosol-foss/python/gri-terrain)
|
|
58
|
+
|
|
59
|
+
Unified terrain elevation data access with fallback chain across multiple sources including DTED, Cloud-Optimized GeoTIFF, and Copernicus DEM.
|
|
60
|
+
|
|
61
|
+
### [GRI Tropo](https://gitlab.com/geosol-foss/python/gri-tropo)
|
|
62
|
+
|
|
63
|
+
Tropospheric delay corrections using ITU-R 2019 models and ERA5 refractivity data.
|
|
64
|
+
|
|
65
|
+
### [GRI Tropo Data](https://gitlab.com/geosol-foss/python/gri-tropo-data)
|
|
66
|
+
|
|
67
|
+
ERA5 data pipeline for downloading and processing tropospheric refractivity data used by gri-tropo.
|
|
68
|
+
|
|
69
|
+
## Layer 3: Applications
|
|
70
|
+
|
|
71
|
+
### [GRI Convolve](https://gitlab.com/geosol-foss/python/gri-convolve)
|
|
72
|
+
|
|
73
|
+
Ellipsoid convolution with outlier detection and clustering, plus Kalman tracking via Interacting Multiple Model (IMM) filter. Combines multiple statistical position estimates into refined solutions. The Kalman tracker supports both ellipsoid-based and raw observable (EKF) updates, bidirectional outlier detection, and online track segmentation.
|
|
74
|
+
|
|
75
|
+
### [GRI GeoSim](https://gitlab.com/geosol-foss/python/gri-geosim)
|
|
76
|
+
|
|
77
|
+
Geometric/geolocation simulation library for platforms, sensors, emitters, and observables. Simulate satellite and ground-based geolocation scenarios with AOA/TDOA/FDOA measurements.
|
|
78
|
+
|
|
79
|
+
### [GRI Sandbox](https://gitlab.com/geosol-foss/python/gri-sandbox)
|
|
80
|
+
|
|
81
|
+
Some neat (small) examples and projects we've put together over time using the above libraries!
|
|
82
|
+
|
|
83
|
+
### [GRI SigSim](https://gitlab.com/geosol-foss/python/gri-sigsim)
|
|
84
|
+
|
|
85
|
+
Object-based RF signal simulation library. Wraps gri-signal functions with chainable object-oriented classes for waveforms (pulsed, PRN, tone), channel modeling (delay, Doppler, multipath), receiver simulation, and TOA estimation.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tracked pre-push hook. Wired up automatically by the hatch build hook on
|
|
3
|
+
# `uv sync` (it sets core.hooksPath to this directory). Delegates to pre-commit
|
|
4
|
+
# in the project's .venv. Unlike a `pre-commit install` generated hook, the
|
|
5
|
+
# python path is resolved at run time, so this file is portable across clones.
|
|
6
|
+
ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-push)
|
|
7
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
ARGS+=(--hook-dir "$HERE" -- "$@")
|
|
9
|
+
|
|
10
|
+
INSTALL_PYTHON="$(git rev-parse --show-toplevel)/.venv/bin/python"
|
|
11
|
+
if [ -x "$INSTALL_PYTHON" ]; then
|
|
12
|
+
exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
|
|
13
|
+
elif command -v pre-commit > /dev/null; then
|
|
14
|
+
exec pre-commit "${ARGS[@]}"
|
|
15
|
+
else
|
|
16
|
+
echo '`pre-commit` not found. Run `uv sync` to set up the environment.' 1>&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
### Python ###
|
|
2
|
+
*.py[cod]
|
|
3
|
+
__pycache__/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
lib/
|
|
8
|
+
lib64/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.venv
|
|
12
|
+
|
|
13
|
+
### Temp files ###
|
|
14
|
+
*~
|
|
15
|
+
*.swp
|
|
16
|
+
.version
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
testreports/
|
|
20
|
+
.VSCodeCounter/
|
|
21
|
+
.coverage
|
|
22
|
+
coverage.xml
|
|
23
|
+
.cov_html/
|
|
24
|
+
*.pstats
|
|
25
|
+
*.stats
|
|
26
|
+
.claude/
|
|
27
|
+
CLAUDE.md
|
|
28
|
+
AGENTS.md
|
|
29
|
+
|
|
30
|
+
### Credentials ###
|
|
31
|
+
.env*
|
|
32
|
+
*.key
|
|
33
|
+
*.pem
|
|
34
|
+
*.p12
|
|
35
|
+
.cdsapirc
|
|
36
|
+
.ecmwfdatastoresrc
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Project-specific dependencies to clone
|
|
2
|
+
# Customize this file per project to clone different repositories
|
|
3
|
+
# TODO: This is only a stopgap until the projects are on pypi. For projects with no
|
|
4
|
+
# dependencies: "before_script: []"
|
|
5
|
+
.clone_dependencies:
|
|
6
|
+
before_script:
|
|
7
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-utils.git ../gri-utils
|
|
8
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-memoize.git ../gri-memoize
|
|
9
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-pos.git ../gri-pos
|
|
10
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-ell.git ../gri-ell
|
|
11
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-nsepoch.git ../gri-nsepoch
|
|
12
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-tropo.git ../gri-tropo
|
|
13
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-iono.git ../gri-iono
|
|
14
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-obs.git ../gri-obs
|
|
15
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-convolve.git ../gri-convolve
|
|
16
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-kalman.git ../gri-kalman
|
|
17
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-trajectory.git ../gri-trajectory
|
|
18
|
+
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-geosim.git ../gri-geosim
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
include:
|
|
2
|
+
- local: .gitlab-ci-deps.yml
|
|
3
|
+
|
|
4
|
+
stages:
|
|
5
|
+
- test
|
|
6
|
+
- lint
|
|
7
|
+
- deploy
|
|
8
|
+
|
|
9
|
+
variables:
|
|
10
|
+
UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
|
|
11
|
+
COVERAGE_THRESHOLD: "70"
|
|
12
|
+
|
|
13
|
+
cache:
|
|
14
|
+
key: "${CI_COMMIT_REF_SLUG}"
|
|
15
|
+
paths:
|
|
16
|
+
- .uv-cache/
|
|
17
|
+
- .venv/
|
|
18
|
+
|
|
19
|
+
.python_base:
|
|
20
|
+
image: python:3.12
|
|
21
|
+
before_script:
|
|
22
|
+
# TODO: This reference and include is a stopgap until we have the projects on pypi
|
|
23
|
+
- !reference [.clone_dependencies, before_script]
|
|
24
|
+
- pip install uv
|
|
25
|
+
- uv sync
|
|
26
|
+
- source .venv/bin/activate
|
|
27
|
+
|
|
28
|
+
test:
|
|
29
|
+
extends: .python_base
|
|
30
|
+
stage: test
|
|
31
|
+
script:
|
|
32
|
+
- pytest --cov --cov-report=term --cov-report=html --cov-report=xml --junitxml=report.xml -v
|
|
33
|
+
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
|
34
|
+
artifacts:
|
|
35
|
+
when: always
|
|
36
|
+
reports:
|
|
37
|
+
junit: report.xml
|
|
38
|
+
coverage_report:
|
|
39
|
+
coverage_format: cobertura
|
|
40
|
+
path: coverage.xml
|
|
41
|
+
paths:
|
|
42
|
+
- report.xml
|
|
43
|
+
- .cov_html/
|
|
44
|
+
- coverage.xml
|
|
45
|
+
expire_in: 30 days
|
|
46
|
+
only:
|
|
47
|
+
- branches
|
|
48
|
+
- merge_requests
|
|
49
|
+
- tags
|
|
50
|
+
|
|
51
|
+
lint:ruff:
|
|
52
|
+
extends: .python_base
|
|
53
|
+
stage: lint
|
|
54
|
+
script:
|
|
55
|
+
- ruff check . --config .ruff.toml
|
|
56
|
+
- ruff format --check . --config .ruff.toml
|
|
57
|
+
allow_failure: false
|
|
58
|
+
only:
|
|
59
|
+
- branches
|
|
60
|
+
- merge_requests
|
|
61
|
+
- tags
|
|
62
|
+
|
|
63
|
+
lint:ty:
|
|
64
|
+
extends: .python_base
|
|
65
|
+
stage: lint
|
|
66
|
+
script:
|
|
67
|
+
- ty check --output-format gitlab --exit-zero > ty-report.json
|
|
68
|
+
- ty check
|
|
69
|
+
allow_failure: false
|
|
70
|
+
artifacts:
|
|
71
|
+
when: always
|
|
72
|
+
reports:
|
|
73
|
+
codequality: ty-report.json
|
|
74
|
+
expire_in: 30 days
|
|
75
|
+
only:
|
|
76
|
+
- branches
|
|
77
|
+
- merge_requests
|
|
78
|
+
- tags
|
|
79
|
+
|
|
80
|
+
deploy:pypi:
|
|
81
|
+
stage: deploy
|
|
82
|
+
image: python:3.12
|
|
83
|
+
id_tokens:
|
|
84
|
+
PYPI_ID_TOKEN:
|
|
85
|
+
aud: pypi
|
|
86
|
+
variables:
|
|
87
|
+
UV_CACHE_DIR: /tmp/uv-cache
|
|
88
|
+
cache: []
|
|
89
|
+
before_script:
|
|
90
|
+
- pip install uv
|
|
91
|
+
script:
|
|
92
|
+
- uv build
|
|
93
|
+
- uv publish
|
|
94
|
+
artifacts:
|
|
95
|
+
paths:
|
|
96
|
+
- dist/
|
|
97
|
+
expire_in: 1 week
|
|
98
|
+
only:
|
|
99
|
+
- tags
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Custom hatchling build hook that wires up the project's git hooks.
|
|
2
|
+
|
|
3
|
+
`uv sync` installs this project in editable mode, which runs the hatchling
|
|
4
|
+
build backend and therefore this hook. The hook points git's ``core.hooksPath``
|
|
5
|
+
at the tracked ``.githooks`` directory, so a fresh clone gets working git hooks
|
|
6
|
+
on its first sync without a separate install step.
|
|
7
|
+
|
|
8
|
+
This only adjusts local git config and is a no-op outside a git work tree (for
|
|
9
|
+
example, in an isolated sdist/wheel build), so it is safe in CI release builds.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GitHooksBuildHook(BuildHookInterface):
|
|
21
|
+
"""Set ``core.hooksPath`` to the tracked ``.githooks`` directory."""
|
|
22
|
+
|
|
23
|
+
PLUGIN_NAME = "custom"
|
|
24
|
+
|
|
25
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None: # noqa: ARG002
|
|
26
|
+
"""Point git at ``.githooks`` if this build runs inside a work tree."""
|
|
27
|
+
root = Path(self.root)
|
|
28
|
+
git = shutil.which("git")
|
|
29
|
+
if git is None or not (root / ".githooks").is_dir():
|
|
30
|
+
return
|
|
31
|
+
# Only act inside a real git work tree; check=False keeps release
|
|
32
|
+
# builds (sdist/wheel in a non-git temp dir) from failing.
|
|
33
|
+
inside = subprocess.run( # noqa: S603
|
|
34
|
+
[git, "rev-parse", "--is-inside-work-tree"],
|
|
35
|
+
cwd=root,
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
check=False,
|
|
39
|
+
)
|
|
40
|
+
if inside.stdout.strip() != "true":
|
|
41
|
+
return
|
|
42
|
+
subprocess.run( # noqa: S603
|
|
43
|
+
[git, "config", "core.hooksPath", ".githooks"],
|
|
44
|
+
cwd=root,
|
|
45
|
+
check=False,
|
|
46
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# See https://pre-commit.com for more information
|
|
2
|
+
# See https://pre-commit.com/hooks.html for more hooks
|
|
3
|
+
# Checks run at pre-push (not pre-commit): commits stay fast, the suite runs
|
|
4
|
+
# once before code is shared. Hooks are wired up automatically on `uv sync`
|
|
5
|
+
# (see .hatch_build.py), so no separate `pre-commit install` step is needed.
|
|
6
|
+
default_stages: [pre-push]
|
|
7
|
+
repos:
|
|
8
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
9
|
+
rev: v8.24.3
|
|
10
|
+
hooks:
|
|
11
|
+
- id: gitleaks
|
|
12
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
13
|
+
# Base pre-commit-provided hooks
|
|
14
|
+
rev: v6.0.0
|
|
15
|
+
hooks:
|
|
16
|
+
- id: trailing-whitespace
|
|
17
|
+
args: [ --markdown-linebreak-ext=md ]
|
|
18
|
+
exclude: '\.dt[012]$'
|
|
19
|
+
- id: end-of-file-fixer
|
|
20
|
+
exclude: '\.dt[012]$'
|
|
21
|
+
- id: check-added-large-files
|
|
22
|
+
- repo: local
|
|
23
|
+
hooks:
|
|
24
|
+
- id: ruff-check
|
|
25
|
+
name: ruff check
|
|
26
|
+
entry: uv run --no-sync ruff check --fix --config .ruff.toml
|
|
27
|
+
language: system
|
|
28
|
+
types: [python]
|
|
29
|
+
- id: ruff-format
|
|
30
|
+
name: ruff format
|
|
31
|
+
entry: uv run --no-sync ruff format
|
|
32
|
+
language: system
|
|
33
|
+
types: [python]
|
|
34
|
+
- id: ty
|
|
35
|
+
name: ty
|
|
36
|
+
entry: uv run --no-sync ty check
|
|
37
|
+
language: system
|
|
38
|
+
types: [python]
|
|
39
|
+
# ty's [tool.ty.src] include scoping is overridden by explicit file
|
|
40
|
+
# args; run without filenames so it checks the configured source set
|
|
41
|
+
# (matching CI) instead of, say, test files passed in by pre-commit.
|
|
42
|
+
pass_filenames: false
|
|
43
|
+
- id: unskipped
|
|
44
|
+
name: Check for pytest skipped tests commented back in
|
|
45
|
+
entry: uv run --no-sync python .pre-commit-unskipped.py
|
|
46
|
+
language: system
|
|
47
|
+
files: '\.py$'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ruff: noqa: T201
|
|
2
|
+
"""Find all the unit tests and make sure none of the skips are commented in!
|
|
3
|
+
|
|
4
|
+
You can run this manually with:
|
|
5
|
+
> python .pre-commit-noskips.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _main() -> None:
|
|
13
|
+
# Get the test directory, which should be adjascent to this script
|
|
14
|
+
script_dir = Path(__file__)
|
|
15
|
+
test_dir = script_dir.parent / "test"
|
|
16
|
+
if not test_dir.exists():
|
|
17
|
+
print("WARNING::: Didn't find a test directory!")
|
|
18
|
+
sys.exit(0)
|
|
19
|
+
tests = test_dir.rglob("test*.py")
|
|
20
|
+
found = False
|
|
21
|
+
for testfile in tests:
|
|
22
|
+
with testfile.open("r") as fp:
|
|
23
|
+
for idx, line in enumerate(fp, 1):
|
|
24
|
+
if line.strip().startswith("#") and (
|
|
25
|
+
"pytest.skip" in line or "pytest.mark.skip" in line
|
|
26
|
+
):
|
|
27
|
+
print(
|
|
28
|
+
f"ERROR::: Line {idx} of {testfile.relative_to(test_dir)} "
|
|
29
|
+
"has a skip commented out:",
|
|
30
|
+
)
|
|
31
|
+
print(f" {idx}: {line}")
|
|
32
|
+
found = True
|
|
33
|
+
if found:
|
|
34
|
+
sys.exit(1) # Error4 aborts the commit
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
_main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
extend = "pyproject.toml"
|
|
2
|
+
|
|
3
|
+
line-length = 88
|
|
4
|
+
|
|
5
|
+
[lint]
|
|
6
|
+
select = ["ALL"]
|
|
7
|
+
|
|
8
|
+
ignore = [
|
|
9
|
+
"COM812", # conflicts with the ruff formatter (trailing comma)
|
|
10
|
+
"ISC001", # conflicts with the ruff formatter (implicit str concat)
|
|
11
|
+
"D105", # Do not require magic method documentation
|
|
12
|
+
"ANN002", # Allow non-typed *args
|
|
13
|
+
"ANN003", # Allow non-typed **kwargs
|
|
14
|
+
"ANN204", # Do not require magic method documentation
|
|
15
|
+
"CPY", # Copyright notices
|
|
16
|
+
"EM101", # raw string in exception
|
|
17
|
+
"EM102", # fstrings in exceptions
|
|
18
|
+
"ERA001", # allow commented out code
|
|
19
|
+
"PERF203",# allow try-except in loops
|
|
20
|
+
"TRY003", # raise vanilla
|
|
21
|
+
"TD002", # TODO author
|
|
22
|
+
"TD003", # TODO link
|
|
23
|
+
"TD004", # TODO colon
|
|
24
|
+
"FIX001", # Fixme
|
|
25
|
+
"FIX002", # TODO
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[lint.extend-per-file-ignores]
|
|
29
|
+
"test/**/*.py" = [
|
|
30
|
+
"D100", # undocumented public module
|
|
31
|
+
"D103", # undocumented public function
|
|
32
|
+
"D104", # undocumented public package
|
|
33
|
+
"ANN001", # no type annotations for function arguments
|
|
34
|
+
"ANN201", # no return type for public function
|
|
35
|
+
"ANN202", # no return type for private test helpers
|
|
36
|
+
"S101", # asserts allowed
|
|
37
|
+
"SLF001", # Allow private member access in unit tests
|
|
38
|
+
"PLR2004", # magic values allowed in comparisons
|
|
39
|
+
"PT011", # pytest.raises can be broad
|
|
40
|
+
"PT013", # allow sub-imports of pytest
|
|
41
|
+
"PLR0913", # allow many arguments in parameterized tests
|
|
42
|
+
]
|
|
43
|
+
"examples/**/*.py" = [
|
|
44
|
+
"D100",
|
|
45
|
+
"INP001", # examples are not a package
|
|
46
|
+
"T201", # allow print in examples
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[lint.pydocstyle]
|
|
50
|
+
convention = "google"
|
|
51
|
+
ignore-var-parameters = true
|
|
52
|
+
ignore-decorators = ["typing.overload"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.testing.pytestArgs": ["test"],
|
|
3
|
+
"python.testing.unittestEnabled": false,
|
|
4
|
+
"python.testing.pytestEnabled": true,
|
|
5
|
+
"editor.rulers": [88],
|
|
6
|
+
"[python]": {
|
|
7
|
+
"editor.formatOnSave": true,
|
|
8
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
9
|
+
"editor.codeActionsOnSave": {
|
|
10
|
+
"source.fixAll": "explicit",
|
|
11
|
+
"source.organizeImports.ruff": "explicit"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! This guide covers everything you need
|
|
4
|
+
to get started.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
Requires **Python 3.12+** and **bash**.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git clone <repo-url>
|
|
12
|
+
cd <repo>
|
|
13
|
+
uv sync
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`uv sync` creates a virtual environment with [uv](https://docs.astral.sh/uv/),
|
|
17
|
+
installs all dependencies (including any editable local siblings), and wires up
|
|
18
|
+
the git hooks automatically: a [hatchling](https://hatch.pypa.io/) build hook
|
|
19
|
+
(`.hatch_build.py`) runs during the editable install and points
|
|
20
|
+
`core.hooksPath` at the tracked `.githooks/` directory. No separate
|
|
21
|
+
`pre-commit install` step is needed. To run commands in the environment, prefix
|
|
22
|
+
them with `uv run` (for example `uv run pytest`), or activate it with
|
|
23
|
+
`source .venv/bin/activate`.
|
|
24
|
+
|
|
25
|
+
To start fresh, delete `.venv/` and run `uv sync` again.
|
|
26
|
+
|
|
27
|
+
> **Note (editable local siblings):** some `gri-*` projects depend on sibling
|
|
28
|
+
> packages as editable path sources. uv requires `[tool.uv]`
|
|
29
|
+
> `config-settings = { editable_mode = "compat" }` in `pyproject.toml` for those
|
|
30
|
+
> to import correctly. If a project declares editable path dependencies and
|
|
31
|
+
> imports fail, verify that setting is present.
|
|
32
|
+
|
|
33
|
+
## Running Tests
|
|
34
|
+
|
|
35
|
+
[pytest](https://docs.pytest.org/) with
|
|
36
|
+
[pytest-cov](https://pypi.org/project/pytest-cov/) for coverage:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pytest --cov # terminal summary
|
|
40
|
+
pytest --cov --cov-report html # detailed HTML report in .cov_html/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
CI requires **70% minimum** coverage. Open the HTML report to see exactly which
|
|
44
|
+
lines are covered:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
xdg-open .cov_html/index.html # or: google-chrome, firefox
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Linting and Type Checking
|
|
51
|
+
|
|
52
|
+
### Ruff
|
|
53
|
+
|
|
54
|
+
[Ruff](https://docs.astral.sh/ruff/) handles both linting and formatting. The
|
|
55
|
+
configuration selects **all rules** (`select = ["ALL"]`) with a small ignore
|
|
56
|
+
list in `.ruff.toml`.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ruff check # lint
|
|
60
|
+
ruff format # format
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### ty
|
|
64
|
+
|
|
65
|
+
[ty](https://docs.astral.sh/ty/) for static type checking:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ty check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Pre-push hooks
|
|
72
|
+
|
|
73
|
+
All of the above run automatically on `git push` via
|
|
74
|
+
[pre-commit](https://pre-commit.com/) (configured for the `pre-push` stage, so
|
|
75
|
+
commits stay fast). If the push fails, fix the issues, commit the fix, and push
|
|
76
|
+
again.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv run pre-commit run --all-files --hook-stage pre-push # run manually
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> **Tip:** To force a push past the hooks (e.g. a work-in-progress branch), use
|
|
83
|
+
> `git push --no-verify`. CI will still enforce the same checks.
|
|
84
|
+
|
|
85
|
+
## Code Style
|
|
86
|
+
|
|
87
|
+
- **Docstrings**: [Google convention](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
|
88
|
+
- **Type hints**: Required on all public function signatures
|
|
89
|
+
- **Line length**: 88 characters max
|
|
90
|
+
- **Formatting**: Let `ruff format` handle it
|
|
91
|
+
- **Imports**: All at the top of the file, organized by ruff's isort rules
|
|
92
|
+
|
|
93
|
+
## Submitting Changes
|
|
94
|
+
|
|
95
|
+
1. Create a branch from `main`
|
|
96
|
+
2. Make your changes with tests
|
|
97
|
+
3. Ensure `pytest --cov` passes with >= 70% coverage
|
|
98
|
+
4. Ensure `uv run pre-commit run --all-files --hook-stage pre-push` passes
|
|
99
|
+
5. Open a merge request against `main`
|
|
100
|
+
|
|
101
|
+
## IDE Setup (VS Code)
|
|
102
|
+
|
|
103
|
+
Install the [Ruff extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)
|
|
104
|
+
by Astral and add to your `settings.json`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"[python]": {
|
|
109
|
+
"editor.formatOnSave": true,
|
|
110
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
111
|
+
"editor.codeActionsOnSave": {
|
|
112
|
+
"source.fixAll": "explicit",
|
|
113
|
+
"source.organizeImports.ruff": "explicit"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For type checking in VS Code, install the
|
|
121
|
+
[ty extension](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty)
|
|
122
|
+
by Astral. To avoid conflicts with Pylance, either let ty disable the Python
|
|
123
|
+
language server (the default) or keep Pylance and set
|
|
124
|
+
`"ty.disableLanguageServices": true` to use ty for diagnostics only.
|