thistle-db 0.6.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.
- thistle_db-0.6.0/.gitignore +133 -0
- thistle_db-0.6.0/CLAUDE.md +148 -0
- thistle_db-0.6.0/LICENSE +7 -0
- thistle_db-0.6.0/PKG-INFO +269 -0
- thistle_db-0.6.0/README.md +252 -0
- thistle_db-0.6.0/example.config.toml +33 -0
- thistle_db-0.6.0/pyproject.toml +50 -0
- thistle_db-0.6.0/scripts/download_tles.py +20 -0
- thistle_db-0.6.0/src/thistle_db/__init__.py +4 -0
- thistle_db-0.6.0/src/thistle_db/_version.py +24 -0
- thistle_db-0.6.0/src/thistle_db/api.py +61 -0
- thistle_db-0.6.0/src/thistle_db/cli.py +286 -0
- thistle_db-0.6.0/src/thistle_db/config.py +118 -0
- thistle_db-0.6.0/src/thistle_db/generator.py +155 -0
- thistle_db-0.6.0/src/thistle_db/ingest.py +312 -0
- thistle_db-0.6.0/src/thistle_db/model.py +198 -0
- thistle_db-0.6.0/src/thistle_db/reader.py +204 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
__about__.py
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
.python-version
|
|
87
|
+
|
|
88
|
+
# pipenv
|
|
89
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
90
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
91
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
92
|
+
# install all needed dependencies.
|
|
93
|
+
#Pipfile.lock
|
|
94
|
+
|
|
95
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
96
|
+
__pypackages__/
|
|
97
|
+
|
|
98
|
+
# Celery stuff
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
celerybeat.pid
|
|
101
|
+
|
|
102
|
+
# SageMath parsed files
|
|
103
|
+
*.sage.py
|
|
104
|
+
|
|
105
|
+
# Environments
|
|
106
|
+
.env
|
|
107
|
+
.venv
|
|
108
|
+
env/
|
|
109
|
+
venv/
|
|
110
|
+
ENV/
|
|
111
|
+
env.bak/
|
|
112
|
+
venv.bak/
|
|
113
|
+
|
|
114
|
+
# Spyder project settings
|
|
115
|
+
.spyderproject
|
|
116
|
+
.spyproject
|
|
117
|
+
|
|
118
|
+
# Rope project settings
|
|
119
|
+
.ropeproject
|
|
120
|
+
|
|
121
|
+
# mkdocs documentation
|
|
122
|
+
/site
|
|
123
|
+
|
|
124
|
+
# mypy
|
|
125
|
+
.mypy_cache/
|
|
126
|
+
.dmypy.json
|
|
127
|
+
dmypy.json
|
|
128
|
+
|
|
129
|
+
# Pyre type checker
|
|
130
|
+
.pyre/
|
|
131
|
+
|
|
132
|
+
# hatch-vcs
|
|
133
|
+
_version.py
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file is the **target design spec** for thistle-db. Where the code and this
|
|
4
|
+
document disagree, this document wins — update the code to match, not the other
|
|
5
|
+
way around. Sections marked **[NOT YET IMPLEMENTED]** are known gaps.
|
|
6
|
+
|
|
7
|
+
## What thistle-db is
|
|
8
|
+
|
|
9
|
+
thistle-db is a database manager for orbital element sets. It ingests TLE
|
|
10
|
+
(Two-Line Element) and OMM (Orbit Mean-Elements Message) files from configured
|
|
11
|
+
directories into a SQL database with uniqueness guarantees, and generates
|
|
12
|
+
organized output files (by date and by satellite) from that database.
|
|
13
|
+
|
|
14
|
+
## Core workflow
|
|
15
|
+
|
|
16
|
+
1. TLE/OMM files are delivered into directories defined in `config.toml`
|
|
17
|
+
(`[[ingest.sources]]` entries: a `path` plus a glob `pattern`).
|
|
18
|
+
2. `thistle-db ingest` scans those directories, auto-detects each file's format,
|
|
19
|
+
parses the element sets, and inserts them into the database. Duplicates are
|
|
20
|
+
silently skipped — ingest is **idempotent**: running it twice over the same
|
|
21
|
+
files must never create duplicate rows or fail.
|
|
22
|
+
3. `thistle-db generate` writes output files from the database.
|
|
23
|
+
|
|
24
|
+
### File formats
|
|
25
|
+
|
|
26
|
+
Format is auto-detected per file (`reader.detect_format`):
|
|
27
|
+
|
|
28
|
+
| Format | Extensions | Reader |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| TLE (2-line or 3-line) | `.tle`, `.txt`, `.3le` | `read_tle` |
|
|
31
|
+
| OMM JSON (Space-Track) | `.json` | `read_omm_json` |
|
|
32
|
+
| OMM CSV | `.csv` | `read_omm_csv` |
|
|
33
|
+
| OMM XML | `.xml` | `read_omm_xml` |
|
|
34
|
+
|
|
35
|
+
### Delivery patterns to handle
|
|
36
|
+
|
|
37
|
+
- **New file per delivery** (e.g. daily `YYYYMMDD.txt`): straightforward scan-and-ingest.
|
|
38
|
+
- **In-place updates**: a provider may append to or rewrite an existing file
|
|
39
|
+
rather than delivering a delta. This must be handled elegantly:
|
|
40
|
+
- Correctness comes from DB-level dedup — re-reading a whole updated file and
|
|
41
|
+
inserting only the new element sets is always safe.
|
|
42
|
+
- Efficiency comes from **file-state tracking**: each successfully ingested
|
|
43
|
+
file's path, size, mtime, and content hash is recorded in the
|
|
44
|
+
`ingest_files` table (`IngestFile` model). On scan
|
|
45
|
+
(`ingest.ingest_source_file`), unchanged files (size + mtime match) are
|
|
46
|
+
skipped without being opened; a changed file whose content hash still
|
|
47
|
+
matches just refreshes its state; otherwise the file is re-ingested (dedup
|
|
48
|
+
absorbs the already-seen records). A file that fails to parse gets no
|
|
49
|
+
state recorded, so it is retried next scan. `--force` bypasses skipping.
|
|
50
|
+
|
|
51
|
+
## Data model (`model.py`)
|
|
52
|
+
|
|
53
|
+
Single canonical element-set table plus an OMM metadata sidecar. Do **not**
|
|
54
|
+
split OMM into a fully separate element-set table.
|
|
55
|
+
|
|
56
|
+
- **`tle`** — one row per unique element set. Stores the raw `line1`/`line2`
|
|
57
|
+
text plus parsed fields (norad_cat_id, epoch, Keplerian elements, drag terms)
|
|
58
|
+
and derived values (semimajor axis, period, apoapsis/periapsis altitude).
|
|
59
|
+
Parsing goes through `sgp4.Satrec`.
|
|
60
|
+
- **`omm_metadata`** — OMM-only fields (object name/type, country code, RCS
|
|
61
|
+
size, launch/decay date, site, originator, GP_ID), one-to-one with a `tle`
|
|
62
|
+
row via `tle_id`.
|
|
63
|
+
- **`ingest_files`** — per-file ingest state (path, size, mtime_ns, sha256)
|
|
64
|
+
used to skip unchanged source files on scan; keyed by a sha256 of the
|
|
65
|
+
resolved path (bounded length for cross-dialect unique indexes).
|
|
66
|
+
|
|
67
|
+
### Uniqueness
|
|
68
|
+
|
|
69
|
+
The dedup key is **exact text of `(line1, line2)`** (a `UniqueConstraint`).
|
|
70
|
+
This is deliberate: it is lossless and simple; textual near-duplicates from
|
|
71
|
+
different providers coexist as separate rows. Do not change the key to
|
|
72
|
+
`(norad_cat_id, epoch)` or similar without an explicit decision.
|
|
73
|
+
|
|
74
|
+
If a TLE is delivered first and the OMM version of the same element set arrives
|
|
75
|
+
later (with identical lines), the TLE row is **not** duplicated — the OMM
|
|
76
|
+
delivery attaches an `omm_metadata` row to the existing `tle` row. Both
|
|
77
|
+
representations are thereby preserved: the TLE lines in `tle`, the OMM extras
|
|
78
|
+
in `omm_metadata`.
|
|
79
|
+
|
|
80
|
+
Dedup is enforced at the database with dialect-aware upserts
|
|
81
|
+
(`ingest._bulk_insert_tles`): `ON CONFLICT DO NOTHING` for SQLite/PostgreSQL,
|
|
82
|
+
`INSERT IGNORE` for MySQL/MariaDB. Inserts are chunked (`CHUNK_SIZE = 5000`).
|
|
83
|
+
Any new bulk-write path must follow this pattern and work on all three dialects.
|
|
84
|
+
|
|
85
|
+
## Configuration (`config.py`)
|
|
86
|
+
|
|
87
|
+
- `config.toml` (path via `-c`, default `./config.toml`), parsed into
|
|
88
|
+
pydantic-settings models: `[database]`, `[[ingest.sources]]`, `[output]`,
|
|
89
|
+
`[logging]`.
|
|
90
|
+
- Database credentials are **never** stored in `config.toml`. Resolution order
|
|
91
|
+
(highest priority first):
|
|
92
|
+
1. Env vars `THISTLE_DB_DATABASE__USERNAME` / `THISTLE_DB_DATABASE__PASSWORD`
|
|
93
|
+
2. User secrets file `~/.config/thistle-db.toml`
|
|
94
|
+
3. System secrets file (`database.secrets_file` in config)
|
|
95
|
+
- Supported databases: SQLite (default/dev), MariaDB/MySQL (operational,
|
|
96
|
+
`thistle-db[mysql]` extra), PostgreSQL (tested in CI).
|
|
97
|
+
|
|
98
|
+
## CLI (`cli.py`)
|
|
99
|
+
|
|
100
|
+
`thistle-db [-c CONFIG] <command>` — the config path defaults to the
|
|
101
|
+
`THISTLE_DB_CONFIG` env var when set (shared with thistle's db fallback),
|
|
102
|
+
else `./config.toml`:
|
|
103
|
+
|
|
104
|
+
- `init` — scaffold `config.toml` and `~/.config/thistle-db.toml`.
|
|
105
|
+
- `ingest [FILES...] [--force]` — ingest specific files (always parsed, even
|
|
106
|
+
if recorded as unchanged), or scan all configured sources when no files are
|
|
107
|
+
given; `--force` re-ingests scanned files regardless of recorded state.
|
|
108
|
+
- `get-tle TARGET [--days N]` — print TLEs to stdout. `TARGET` is either an
|
|
109
|
+
8-digit date `YYYYMMDD` (→ the nearest TLE per object to 12:00 UTC on that
|
|
110
|
+
date, within ±N days, default 7) or an alpha-5-compatible NORAD ID
|
|
111
|
+
(e.g. `25544`, `00022`, `E5693` → every TLE for that object, epoch order).
|
|
112
|
+
Exits 1 if nothing matches.
|
|
113
|
+
- `generate` — write output files per `[output]` config:
|
|
114
|
+
- `date_files`: `YYYYMMDD.{tle,omm}` — latest element set per satellite for
|
|
115
|
+
that date.
|
|
116
|
+
- `object_files`: `NORAD_ID.{tle,omm}` — all element sets for a satellite,
|
|
117
|
+
ordered by epoch.
|
|
118
|
+
- Formats toggleable via `[output.formats]` (tle = two-line text, omm = CSV).
|
|
119
|
+
|
|
120
|
+
## Module map
|
|
121
|
+
|
|
122
|
+
| Module | Responsibility |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `cli.py` | typer CLI, subcommand dispatch, logging setup |
|
|
125
|
+
| `config.py` | pydantic-settings config + layered secrets resolution |
|
|
126
|
+
| `model.py` | SQLAlchemy ORM models (`TLE`, `OmmMetadata`), TLE parsing via sgp4 |
|
|
127
|
+
| `reader.py` | format detection + file readers (TLE, OMM JSON/CSV/XML) |
|
|
128
|
+
| `ingest.py` | bulk insert with dedup, source-directory scanning |
|
|
129
|
+
| `generator.py` | output file generation from the database |
|
|
130
|
+
|
|
131
|
+
## Error handling philosophy
|
|
132
|
+
|
|
133
|
+
Ingest is tolerant: a malformed TLE or OMM record is logged (loguru, WARNING)
|
|
134
|
+
and skipped — one bad record must never abort a file, and one bad file must
|
|
135
|
+
never abort a scan. A missing source directory is a warning, not an error.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
- Python ≥ 3.11. Package manager: `uv`. Build: hatchling + hatch-vcs
|
|
140
|
+
(version comes from git tags — never hardcode it).
|
|
141
|
+
- Run tests: `uv run pytest tests/thistle_db` from the workspace root. Lint:
|
|
142
|
+
`uv run ruff check`. Types: `uv run pyright`.
|
|
143
|
+
- Tests cover SQLite by default; set `THISTLE_DB_TEST_MARIADB=1` /
|
|
144
|
+
`THISTLE_DB_TEST_POSTGRES=1` (requires Docker) to run the MariaDB and
|
|
145
|
+
PostgreSQL backends via testcontainers — CI runs all three. Dialect-specific
|
|
146
|
+
behavior (upserts) must be tested against all three.
|
|
147
|
+
- Test fixtures live in `tests/thistle_db/data/` (real TLE text files and
|
|
148
|
+
Space-Track OMM JSON).
|
thistle_db-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 Jonathan Olsten
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thistle-db
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Orbital element database management tool
|
|
5
|
+
Author-email: Jonathan Olsten <jolsten@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: loguru>=0.7.3
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.9.1
|
|
11
|
+
Requires-Dist: sgp4>=2.24
|
|
12
|
+
Requires-Dist: sqlalchemy>=2.0.40
|
|
13
|
+
Requires-Dist: typer>=0.27.0
|
|
14
|
+
Provides-Extra: mysql
|
|
15
|
+
Requires-Dist: pymysql>=1.1; extra == 'mysql'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# thistle-db
|
|
19
|
+
|
|
20
|
+
Orbital element database manager. Ingests TLE (Two-Line Element) and OMM (Orbit Mean-Elements Message) files into a database and generates organized output files by date and satellite.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install thistle-db
|
|
26
|
+
|
|
27
|
+
# For MariaDB/MySQL support:
|
|
28
|
+
pip install thistle-db[mysql]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv add thistle-db
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Scaffold configuration
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
thistle-db init
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This creates two files:
|
|
46
|
+
|
|
47
|
+
- `./config.toml` -- main configuration (database, ingest sources, output settings)
|
|
48
|
+
- `~/.config/thistle-db.toml` -- user-local database credentials
|
|
49
|
+
|
|
50
|
+
Use `-c` to specify a different config path:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
thistle-db -c /etc/thistle-db/config.toml init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Configure
|
|
57
|
+
|
|
58
|
+
Edit `config.toml` to set your database and ingest sources. The generated file is fully commented -- see below for a summary.
|
|
59
|
+
|
|
60
|
+
**SQLite (default):**
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[database]
|
|
64
|
+
drivername = "sqlite"
|
|
65
|
+
name = "thistle-db.db"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**MariaDB/MySQL:**
|
|
69
|
+
|
|
70
|
+
```toml
|
|
71
|
+
[database]
|
|
72
|
+
drivername = "mysql+pymysql"
|
|
73
|
+
host = "localhost"
|
|
74
|
+
port = 3306
|
|
75
|
+
name = "thistle-db"
|
|
76
|
+
secrets_file = "/etc/thistle-db/secrets.toml"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then add your credentials to `~/.config/thistle-db.toml`:
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
username = "myuser"
|
|
83
|
+
password = "mypassword"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Ingest TLE/OMM files
|
|
87
|
+
|
|
88
|
+
Scan configured source directories:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
thistle-db ingest
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Or ingest specific files:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
thistle-db ingest /path/to/20260327.tle /path/to/20260327.json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
File format is auto-detected by extension:
|
|
101
|
+
|
|
102
|
+
| Extension | Format |
|
|
103
|
+
| ------------------ | ------------------- |
|
|
104
|
+
| `.tle`, `.txt`, `.3le` | Two-Line Element |
|
|
105
|
+
| `.json` | Space-Track OMM JSON |
|
|
106
|
+
| `.csv` | OMM CSV |
|
|
107
|
+
| `.xml` | OMM XML |
|
|
108
|
+
|
|
109
|
+
Ingestion is idempotent -- duplicate records are silently skipped.
|
|
110
|
+
|
|
111
|
+
### 4. Generate output files
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
thistle-db generate
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This produces files in the configured output directory:
|
|
118
|
+
|
|
119
|
+
- **Date files** (`YYYYMMDD.tle` / `YYYYMMDD.omm`) -- one TLE per satellite for each date (latest epoch that day)
|
|
120
|
+
- **Object files** (`25544.tle` / `25544.omm`) -- all TLEs for a single satellite, ordered by epoch
|
|
121
|
+
|
|
122
|
+
## Automating with Cron
|
|
123
|
+
|
|
124
|
+
thistle-db is designed to run via cron rather than as a long-running service. Both `ingest` and `generate` are idempotent and safe to re-run.
|
|
125
|
+
|
|
126
|
+
**Ingest and generate every 4 hours:**
|
|
127
|
+
|
|
128
|
+
```cron
|
|
129
|
+
0 */4 * * * thistle-db -c /etc/thistle-db/config.toml ingest && thistle-db -c /etc/thistle-db/config.toml generate
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Ingest hourly, generate once daily at 03:00 UTC:**
|
|
133
|
+
|
|
134
|
+
```cron
|
|
135
|
+
0 * * * * thistle-db -c /etc/thistle-db/config.toml ingest
|
|
136
|
+
0 3 * * * thistle-db -c /etc/thistle-db/config.toml generate
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**With logging to a file:**
|
|
140
|
+
|
|
141
|
+
```cron
|
|
142
|
+
0 */4 * * * thistle-db -c /etc/thistle-db/config.toml ingest >> /var/log/thistle-db.log 2>&1 && thistle-db -c /etc/thistle-db/config.toml generate >> /var/log/thistle-db.log 2>&1
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Credential Resolution
|
|
146
|
+
|
|
147
|
+
Database credentials are resolved in priority order:
|
|
148
|
+
|
|
149
|
+
1. **Environment variables** -- `THISTLE_DB_DATABASE__USERNAME` / `THISTLE_DB_DATABASE__PASSWORD`
|
|
150
|
+
2. **User secrets file** -- `~/.config/thistle-db.toml`
|
|
151
|
+
3. **System secrets file** -- path set via `secrets_file` in `config.toml`
|
|
152
|
+
4. **config.toml values** -- not recommended for credentials
|
|
153
|
+
|
|
154
|
+
For cron jobs, either use the user secrets file or export environment variables in the crontab:
|
|
155
|
+
|
|
156
|
+
```cron
|
|
157
|
+
THISTLE_DB_DATABASE__USERNAME=myuser
|
|
158
|
+
THISTLE_DB_DATABASE__PASSWORD=mypassword
|
|
159
|
+
0 */4 * * * thistle-db -c /etc/thistle-db/config.toml ingest && thistle-db -c /etc/thistle-db/config.toml generate
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## CLI Reference
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
thistle-db [-c CONFIG] COMMAND
|
|
166
|
+
|
|
167
|
+
Commands:
|
|
168
|
+
init Scaffold config.toml and ~/.config/thistle-db.toml
|
|
169
|
+
ingest Ingest TLE/OMM files into the database
|
|
170
|
+
generate Generate output TLE/OMM files from the database
|
|
171
|
+
get-tle Print TLEs from the database to stdout
|
|
172
|
+
|
|
173
|
+
Options:
|
|
174
|
+
-c, --config PATH Path to config.toml
|
|
175
|
+
(default: $THISTLE_DB_CONFIG if set, else ./config.toml)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `get-tle`
|
|
179
|
+
|
|
180
|
+
Query the database directly and print TLEs to stdout. The positional argument
|
|
181
|
+
is either a NORAD ID (alpha-5 compatible, e.g. `25544`, `00022`, `E5693`) or
|
|
182
|
+
an 8-digit date (`YYYYMMDD`):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# All TLEs for one satellite, ordered by epoch
|
|
186
|
+
thistle-db get-tle 25544
|
|
187
|
+
thistle-db get-tle E5693 # alpha-5 IDs work too (= 145693)
|
|
188
|
+
|
|
189
|
+
# Nearest TLE per satellite to 12:00 UTC on a date, within +/- 7 days
|
|
190
|
+
thistle-db get-tle 20260717
|
|
191
|
+
|
|
192
|
+
# Widen (or narrow) the search window
|
|
193
|
+
thistle-db get-tle 20260717 --days 3
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Exits with status 1 if no TLEs match.
|
|
197
|
+
|
|
198
|
+
## Configuration Reference
|
|
199
|
+
|
|
200
|
+
### `[database]`
|
|
201
|
+
|
|
202
|
+
| Field | Default | Description |
|
|
203
|
+
| -------------- | ------------ | ------------------------------------------ |
|
|
204
|
+
| `drivername` | `"sqlite"` | SQLAlchemy driver (`sqlite`, `mysql+pymysql`) |
|
|
205
|
+
| `name` | `":memory:"` | Database name or file path |
|
|
206
|
+
| `host` | | Database host |
|
|
207
|
+
| `port` | | Database port |
|
|
208
|
+
| `username` | | Database username (prefer secrets file) |
|
|
209
|
+
| `password` | | Database password (prefer secrets file) |
|
|
210
|
+
| `secrets_file` | | Path to a TOML file with username/password |
|
|
211
|
+
|
|
212
|
+
### `[[ingest.sources]]`
|
|
213
|
+
|
|
214
|
+
| Field | Default | Description |
|
|
215
|
+
| --------- | --------- | ---------------------------------- |
|
|
216
|
+
| `path` | | Directory to scan for files |
|
|
217
|
+
| `pattern` | `"*.tle"` | Glob pattern for matching files |
|
|
218
|
+
|
|
219
|
+
### `[output]`
|
|
220
|
+
|
|
221
|
+
| Field | Default | Description |
|
|
222
|
+
| ------- | ------------ | ------------------------------- |
|
|
223
|
+
| `dir` | `"./output"` | Output directory |
|
|
224
|
+
|
|
225
|
+
### `[output.formats]`
|
|
226
|
+
|
|
227
|
+
| Field | Default | Description |
|
|
228
|
+
| ----- | ------- | --------------------------- |
|
|
229
|
+
| `tle` | `true` | Generate .tle output files |
|
|
230
|
+
| `omm` | `true` | Generate .omm (CSV) output |
|
|
231
|
+
|
|
232
|
+
### `[output.types]`
|
|
233
|
+
|
|
234
|
+
| Field | Default | Description |
|
|
235
|
+
| -------------- | ------- | -------------------------------------------------- |
|
|
236
|
+
| `date_files` | `true` | YYYYMMDD files with latest TLE per satellite |
|
|
237
|
+
| `object_files` | `true` | Per-satellite files with all TLEs ordered by epoch |
|
|
238
|
+
|
|
239
|
+
### `[logging]`
|
|
240
|
+
|
|
241
|
+
| Field | Default | Description |
|
|
242
|
+
| ------- | -------- | ---------------------------------------------- |
|
|
243
|
+
| `level` | `"INFO"` | Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL |
|
|
244
|
+
|
|
245
|
+
## Development
|
|
246
|
+
|
|
247
|
+
### Running tests
|
|
248
|
+
|
|
249
|
+
Tests live at the workspace root under `tests/thistle_db/` and are
|
|
250
|
+
parametrized to run against SQLite, MariaDB, and PostgreSQL. SQLite runs
|
|
251
|
+
unconditionally; the MariaDB and PostgreSQL backends are opt-in and managed
|
|
252
|
+
automatically by [testcontainers](https://testcontainers-python.readthedocs.io/)
|
|
253
|
+
— one container per test session, one throwaway database per test. No manual
|
|
254
|
+
`docker run` needed, just a running Docker daemon.
|
|
255
|
+
|
|
256
|
+
SQLite only:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
uv run pytest tests/thistle_db
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
All backends (requires Docker):
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
THISTLE_DB_TEST_MARIADB=1 THISTLE_DB_TEST_POSTGRES=1 uv run pytest tests/thistle_db
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The images default to `mariadb:11` and `postgres:16`; override with
|
|
269
|
+
`THISTLE_DB_MARIADB_IMAGE` / `THISTLE_DB_POSTGRES_IMAGE`.
|