mastodown 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.
@@ -0,0 +1,28 @@
1
+ # Mastodown Copilot Instructions
2
+
3
+ ## Commands
4
+
5
+ - Set up the development environment with `uv sync`.
6
+ - Run all tests with `uv run pytest`.
7
+ - Run one test with `uv run pytest tests/test_query.py::QueryObservationTests::test_query_uses_date_range_filters_and_max_entries`.
8
+ - Build documentation, including generated API and CLI reference pages, with `uv run make -C docs html`. The build treats warnings as errors.
9
+ - Check that the committed CLI reference matches `mastodown --help` with `uv run python docs/generate_cli_reference.py --check`.
10
+
11
+ Python 3.11 or later is required. When using a pip-installed environment rather than uv, omit `uv run`.
12
+
13
+ ## Architecture
14
+
15
+ - The package uses a `src/` layout. The `mastodown` console command is registered in `pyproject.toml` and calls `mastodown.cli:main`.
16
+ - `cli.py` owns argparse setup and orchestration. Both `query` and `download` use the same query arguments; `run_query()` delegates to `query_obs()`, prints its dataframe, and optionally writes CSV. `download` then asks for confirmation unless `--yes` is supplied.
17
+ - `query.py` translates CLI/library filters into `astroquery.mast.Observations` calls for JWST. It fetches observations, gets and filters their products, joins `target_name` back from the observation table, and returns a pandas dataframe with `target_name` as its first column. Target-acquisition filtering uses the JWST mission metadata interface unless `keep_ta=True`.
18
+ - `download.py` consumes that dataframe and requires its product, proposal, target, and data URI columns. It derives `local_path`, compares it with `<download-dir>/manifest.csv`, downloads only required products, and updates the manifest only with successful downloads. Proposal directories are enabled by default; target directories use `target_directory_name()` to produce filesystem-safe names.
19
+ - Sphinx documentation is in `docs/`. `docs/Makefile` runs `generate_api_reference.py` before HTML builds, while `docs/conf.py` regenerates `cli-reference.md` from `cli.py` at Sphinx startup. `docs/cli-reference.md` is generated output; change the parser or generator rather than editing its content manually.
20
+
21
+ ## Repository Conventions
22
+
23
+ - Keep the CLI parser, `COMMAND_NAMES`, generated CLI reference, and CLI reference tests aligned whenever commands or options change. Run the freshness check after parser changes.
24
+ - Preserve the shared argument forwarding contract between `query` and `download`: `add_query_arguments()` defines it once, and `run_query()` is the only CLI-to-`query_obs()` bridge.
25
+ - Authentication is intentionally centralized in `authenticate()`: use `MAST_API_TOKEN` when present; only trigger Astroquery's interactive login when `--auth` is set and no environment token exists.
26
+ - Query validation is library-level: date bounds must be supplied together in exact `YYYY-MM-DD` form, end dates are inclusive, and `max_entries` must be positive. Tests mock Astroquery network methods, so new query behavior should remain testable without live MAST access.
27
+ - Treat `manifest.csv` as the download state source of truth. Reconcile missing files already recorded in it, add already-present unrecorded files, and do not record failed downloads. `dry_run=True` must avoid filesystem writes, including the manifest.
28
+ - Tests use `unittest.TestCase` with `unittest.mock` and `pytest` as the runner. Patch dependencies at their use site (for example, `mastodown.query.Observations` or `mastodown.cli.query_obs`) and use temporary directories for download filesystem behavior.
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ src/mastho/_version.py
5
+ build/
6
+ dist/
7
+ wheels/
8
+ *.egg-info
9
+ _version.py
10
+
11
+ # Virtual environments
12
+ .venv
13
+ docs/_build/
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,18 @@
1
+ # Read the Docs configuration file for Mastodown.
2
+ version: 2
3
+
4
+ build:
5
+ os: ubuntu-24.04
6
+ tools:
7
+ python: "3.13"
8
+
9
+ python:
10
+ install:
11
+ - method: uv
12
+ command: sync
13
+ groups:
14
+ - docs
15
+
16
+ sphinx:
17
+ configuration: docs/conf.py
18
+ fail_on_warning: true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Vandal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.5
2
+ Name: mastodown
3
+ Version: 0.1.0
4
+ Summary: Tiny Python MAST Client
5
+ Author-email: Thomas Vandal <thomas.vandal@umontreal.ca>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: astroquery
9
+ Requires-Dist: pandas
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Mastodown
13
+
14
+ Mastodown is a small Python client for querying and downloading data from the
15
+ [MAST](https://mast.stsci.edu/) archive on the command line or from Python.
16
+
17
+ Read the [documentation](https://mastodown.readthedocs.io/) for installation,
18
+ command-line usage, authentication, and the Python API reference.
19
+
20
+ ## Quick start
21
+
22
+ ```bash
23
+ python -m pip install git+https://github.com/vandalt/mastodown.git
24
+ mastodown query --programs 01200 02473 --calib-level 1 --product-type SCIENCE --extension fits -o products.csv
25
+ ```
26
+
27
+ See the [CLI guide](https://mastodown.readthedocs.io/en/latest/cli.html) for all
28
+ commands and options.
@@ -0,0 +1,17 @@
1
+ # Mastodown
2
+
3
+ Mastodown is a small Python client for querying and downloading data from the
4
+ [MAST](https://mast.stsci.edu/) archive on the command line or from Python.
5
+
6
+ Read the [documentation](https://mastodown.readthedocs.io/) for installation,
7
+ command-line usage, authentication, and the Python API reference.
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ python -m pip install git+https://github.com/vandalt/mastodown.git
13
+ mastodown query --programs 01200 02473 --calib-level 1 --product-type SCIENCE --extension fits -o products.csv
14
+ ```
15
+
16
+ See the [CLI guide](https://mastodown.readthedocs.io/en/latest/cli.html) for all
17
+ commands and options.
@@ -0,0 +1,19 @@
1
+ SPHINXOPTS ?=
2
+ SPHINXBUILD ?= sphinx-build
3
+ PYTHON = python
4
+ SOURCEDIR = .
5
+ BUILDDIR = _build
6
+
7
+ help:
8
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
9
+
10
+ api:
11
+ @$(PYTHON) generate_api_reference.py
12
+
13
+ html: api
14
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" -W $(SPHINXOPTS) $(O)
15
+
16
+ .PHONY: help api html Makefile
17
+
18
+ %: Makefile
19
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" -W $(SPHINXOPTS) $(O)
@@ -0,0 +1,9 @@
1
+ API Reference
2
+ =============
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ mastodown.cli
8
+ mastodown.download
9
+ mastodown.query
@@ -0,0 +1,8 @@
1
+ mastodown.cli module
2
+ ====================
3
+
4
+ .. automodule:: mastodown.cli
5
+ :members:
6
+ :exclude-members: ArgumentParser, DataFrame, InvalidQueryError, MastMissions, Namespace, Observations, Path, Series, Time, concat, datetime, environ, read_csv, sub, timedelta
7
+ :show-inheritance:
8
+ :undoc-members:
@@ -0,0 +1,8 @@
1
+ mastodown.download module
2
+ =========================
3
+
4
+ .. automodule:: mastodown.download
5
+ :members:
6
+ :exclude-members: ArgumentParser, DataFrame, InvalidQueryError, MastMissions, Namespace, Observations, Path, Series, Time, concat, datetime, environ, read_csv, sub, timedelta
7
+ :show-inheritance:
8
+ :undoc-members:
@@ -0,0 +1,8 @@
1
+ mastodown.query module
2
+ ======================
3
+
4
+ .. automodule:: mastodown.query
5
+ :members:
6
+ :exclude-members: ArgumentParser, DataFrame, InvalidQueryError, MastMissions, Namespace, Observations, Path, Series, Time, concat, datetime, environ, read_csv, sub, timedelta
7
+ :show-inheritance:
8
+ :undoc-members:
@@ -0,0 +1,55 @@
1
+ # Command line recipes
2
+
3
+ For complete option details, see the {doc}`command reference <cli-reference>`.
4
+
5
+ ## Viewing MAST metadata columns
6
+
7
+ List metadata for all available MAST observation columns:
8
+
9
+ ```bash
10
+ mastodown get-meta
11
+ ```
12
+
13
+ ## Querying products
14
+
15
+ Query JWST products and save the resulting table to CSV:
16
+
17
+ ```bash
18
+ mastodown query --programs 01200 02473 --calib-level 1 --product-type SCIENCE --extension fits -o products.csv
19
+ ```
20
+
21
+ Filter options accept one or more space-separated values. Use `--keep-ta` to
22
+ retain target-acquisition observations and `--verbose` to print observation
23
+ details. Use `--filters` for MAST instrument filters,
24
+ `--date-range START_DATE END_DATE` for inclusive `YYYY-MM-DD` observation
25
+ dates, and `--max-entries` to limit returned observations.
26
+
27
+ ## Downloading products
28
+
29
+ Query products and download them after confirmation:
30
+
31
+ ```bash
32
+ mastodown download --programs 01200 02473 --download-dir data --dry-run
33
+ ```
34
+
35
+ The download command prompts after showing the query result; press Enter to
36
+ continue, enter `n` to cancel, or pass `-y` / `--yes` to skip the prompt. Use
37
+ `--query-output products.csv` to save the query result, and
38
+ `--no-proposal-subdir` to save files directly in the download directory. Use
39
+ `--target-subdir` to place each product under its target name, after the
40
+ optional proposal directory.
41
+
42
+ (proprietary-data-without-an-environment-variable)=
43
+ ## Accessing proprietary data without an environment variable
44
+
45
+ As described in the {ref}`installation docs <accessing-proprietary-data>`,
46
+ the simplest way to access proprietary data is by storing your MAST API token in an environment variable.
47
+
48
+ Alternatively, you can pass `--auth` to securely enter a token through Astroquery's prompt:
49
+
50
+ ```bash
51
+ mastodown query --programs 01200 --auth
52
+ ```
53
+
54
+ The environment token takes precedence over `--auth`.
55
+ Do not pass tokens directly on the command line.
@@ -0,0 +1,127 @@
1
+ # Command reference
2
+
3
+ This reference is generated from the CLI's `--help` output. Do not edit it manually.
4
+
5
+ ## `mastodown`
6
+
7
+ ```console
8
+ $ mastodown --help
9
+ usage: mastodown [-h] {get-meta,query,download} ...
10
+
11
+ Tiny Python MAST client.
12
+
13
+ positional arguments:
14
+ {get-meta,query,download}
15
+ get-meta List metadata for all MAST observation columns. Should
16
+ match the web portal interface.
17
+ query Query the MAST observations portal.
18
+ download Query the MAST observations portal and download the
19
+ matching products.
20
+
21
+ options:
22
+ -h, --help show this help message and exit
23
+ ```
24
+
25
+ ## `mastodown get-meta`
26
+
27
+ ```console
28
+ $ mastodown get-meta --help
29
+ usage: mastodown get-meta [-h]
30
+
31
+ List metadata for all MAST observation columns. Should match the web portal
32
+ interface.
33
+
34
+ options:
35
+ -h, --help show this help message and exit
36
+ ```
37
+
38
+ ## `mastodown query`
39
+
40
+ ```console
41
+ $ mastodown query --help
42
+ usage: mastodown query [-h] [--programs PROGRAMS [PROGRAMS ...]]
43
+ [--calib-level CALIB_LEVEL [CALIB_LEVEL ...]]
44
+ [--product-type PRODUCT_TYPE [PRODUCT_TYPE ...]]
45
+ [--extension EXTENSION [EXTENSION ...]]
46
+ [--filters FILTERS [FILTERS ...]]
47
+ [--date-range START_DATE END_DATE]
48
+ [--max-entries MAX_ENTRIES] [--keep-ta] [--verbose]
49
+ [--auth] [-o QUERY_OUTPUT]
50
+
51
+ Query the MAST observations portal.
52
+
53
+ options:
54
+ -h, --help show this help message and exit
55
+ --programs PROGRAMS [PROGRAMS ...]
56
+ Proposal IDs to query.
57
+ --calib-level CALIB_LEVEL [CALIB_LEVEL ...]
58
+ Calibration levels of products to include.
59
+ --product-type PRODUCT_TYPE [PRODUCT_TYPE ...]
60
+ Product types to include.
61
+ --extension EXTENSION [EXTENSION ...]
62
+ File extensions to include.
63
+ --filters FILTERS [FILTERS ...]
64
+ MAST instrument filters to include.
65
+ --date-range START_DATE END_DATE
66
+ Inclusive observation dates in YYYY-MM-DD format.
67
+ --max-entries MAX_ENTRIES
68
+ Maximum number of observations to return.
69
+ --keep-ta Keep target-acquisition observations.
70
+ --verbose Print matching observations and product summary
71
+ details.
72
+ --auth Prompt securely for a MAST API token when
73
+ MAST_API_TOKEN is not set.
74
+ -o, --output QUERY_OUTPUT
75
+ Write the resulting dataframe to this CSV path.
76
+ ```
77
+
78
+ ## `mastodown download`
79
+
80
+ ```console
81
+ $ mastodown download --help
82
+ usage: mastodown download [-h] [--programs PROGRAMS [PROGRAMS ...]]
83
+ [--calib-level CALIB_LEVEL [CALIB_LEVEL ...]]
84
+ [--product-type PRODUCT_TYPE [PRODUCT_TYPE ...]]
85
+ [--extension EXTENSION [EXTENSION ...]]
86
+ [--filters FILTERS [FILTERS ...]]
87
+ [--date-range START_DATE END_DATE]
88
+ [--max-entries MAX_ENTRIES] [--keep-ta] [--verbose]
89
+ [--auth] [--query-output QUERY_OUTPUT]
90
+ [--download-dir DOWNLOAD_DIR] [--overwrite]
91
+ [--dry-run] [--no-proposal-subdir] [--target-subdir]
92
+ [-y]
93
+
94
+ Query the MAST observations portal and download the matching products.
95
+
96
+ options:
97
+ -h, --help show this help message and exit
98
+ --programs PROGRAMS [PROGRAMS ...]
99
+ Proposal IDs to query.
100
+ --calib-level CALIB_LEVEL [CALIB_LEVEL ...]
101
+ Calibration levels of products to include.
102
+ --product-type PRODUCT_TYPE [PRODUCT_TYPE ...]
103
+ Product types to include.
104
+ --extension EXTENSION [EXTENSION ...]
105
+ File extensions to include.
106
+ --filters FILTERS [FILTERS ...]
107
+ MAST instrument filters to include.
108
+ --date-range START_DATE END_DATE
109
+ Inclusive observation dates in YYYY-MM-DD format.
110
+ --max-entries MAX_ENTRIES
111
+ Maximum number of observations to return.
112
+ --keep-ta Keep target-acquisition observations.
113
+ --verbose Print matching observations and product summary
114
+ details.
115
+ --auth Prompt securely for a MAST API token when
116
+ MAST_API_TOKEN is not set.
117
+ --query-output QUERY_OUTPUT
118
+ Write the resulting dataframe to this CSV path.
119
+ --download-dir DOWNLOAD_DIR
120
+ Directory in which to save downloaded products.
121
+ --overwrite Download products even when they already exist.
122
+ --dry-run List planned downloads without writing files.
123
+ --no-proposal-subdir Save products directly in the download directory.
124
+ --target-subdir Group downloaded products into target-name
125
+ subdirectories.
126
+ -y, --yes Download without prompting for confirmation.
127
+ ```
@@ -0,0 +1,39 @@
1
+ """Configuration for the Mastodown Sphinx documentation."""
2
+
3
+ from pathlib import Path
4
+ import sys
5
+
6
+ DOCS_DIR = Path(__file__).resolve().parent
7
+ sys.path.insert(0, str(DOCS_DIR))
8
+ sys.path.insert(0, str(DOCS_DIR.parent / "src"))
9
+
10
+ project = "Mastodown"
11
+ copyright = "2026, Thomas Vandal"
12
+ author = "Thomas Vandal"
13
+
14
+ extensions = [
15
+ "myst_parser",
16
+ "sphinx.ext.autodoc",
17
+ "sphinx.ext.viewcode",
18
+ "sphinx_autodoc_typehints",
19
+ ]
20
+
21
+ source_suffix = {
22
+ ".rst": "restructuredtext",
23
+ ".md": "markdown",
24
+ }
25
+
26
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
27
+ html_theme = "furo"
28
+
29
+
30
+ def generate_cli_reference(_: object) -> None:
31
+ """Refresh generated CLI help before Sphinx reads source files."""
32
+ from generate_cli_reference import write_reference
33
+
34
+ write_reference()
35
+
36
+
37
+ def setup(app: object) -> None:
38
+ """Register documentation build hooks."""
39
+ app.connect("builder-inited", generate_cli_reference)
@@ -0,0 +1,71 @@
1
+ """Generate API reference pages from the Mastodown package."""
2
+
3
+ from pathlib import Path
4
+ import subprocess
5
+ import sys
6
+
7
+ DOCS_DIR = Path(__file__).resolve().parent
8
+ PROJECT_DIR = DOCS_DIR.parent
9
+ API_DIR = DOCS_DIR / "api"
10
+ PACKAGE_DIR = PROJECT_DIR / "src" / "mastodown"
11
+ INDEX_PATH = API_DIR / "index.rst"
12
+
13
+
14
+ def main() -> None:
15
+ """Generate module pages and an API-reference toctree."""
16
+ API_DIR.mkdir(exist_ok=True)
17
+
18
+ for path in API_DIR.glob("mastodown*.rst"):
19
+ path.unlink()
20
+
21
+ subprocess.run(
22
+ [
23
+ sys.executable,
24
+ "-m",
25
+ "sphinx.ext.apidoc",
26
+ "--separate",
27
+ "--no-toc",
28
+ "--force",
29
+ "-o",
30
+ str(API_DIR),
31
+ str(PACKAGE_DIR),
32
+ ],
33
+ check=True,
34
+ )
35
+
36
+ # The package overview duplicates the flat API index's role and introduces
37
+ # duplicate toctree references for each module.
38
+ (API_DIR / "mastodown.rst").unlink()
39
+ modules = sorted(path.stem for path in API_DIR.glob("mastodown.*.rst"))
40
+ for module in modules:
41
+ exclude_imported_members(API_DIR / f"{module}.rst")
42
+ INDEX_PATH.write_text(
43
+ "API Reference\n"
44
+ "=============\n"
45
+ "\n"
46
+ ".. toctree::\n"
47
+ " :maxdepth: 1\n"
48
+ "\n"
49
+ + "".join(f" {module}\n" for module in modules),
50
+ encoding="utf-8",
51
+ )
52
+
53
+
54
+ def exclude_imported_members(path: Path) -> None:
55
+ """Keep autodoc focused on Mastodown's public objects."""
56
+ content = path.read_text(encoding="utf-8")
57
+ content = content.replace(
58
+ " :members:\n",
59
+ (
60
+ " :members:\n"
61
+ " :exclude-members: ArgumentParser, DataFrame, InvalidQueryError, "
62
+ "MastMissions, Namespace, Observations, Path, Series, Time, concat, "
63
+ "datetime, environ, read_csv, sub, timedelta\n"
64
+ ),
65
+ 1,
66
+ )
67
+ path.write_text(content, encoding="utf-8")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
@@ -0,0 +1,76 @@
1
+ """Generate the command-line reference from Mastodown's argument parser."""
2
+
3
+ from argparse import ArgumentParser
4
+ from pathlib import Path
5
+ import re
6
+ import sys
7
+
8
+ DOCS_DIR = Path(__file__).resolve().parent
9
+ PROJECT_DIR = DOCS_DIR.parent
10
+ REFERENCE_PATH = DOCS_DIR / "cli-reference.md"
11
+ ANSI_ESCAPE_PATTERN = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
12
+
13
+ sys.path.insert(0, str(PROJECT_DIR / "src"))
14
+
15
+ from mastodown.cli import COMMAND_NAMES, create_parser
16
+
17
+
18
+ def format_command_help(parser: ArgumentParser, command: str | None = None) -> str:
19
+ """Return formatted help for the root parser or one subcommand."""
20
+ if command is None:
21
+ return ANSI_ESCAPE_PATTERN.sub("", parser.format_help()).rstrip()
22
+
23
+ for action in parser._actions:
24
+ choices = getattr(action, "choices", None)
25
+ if choices is not None and command in choices:
26
+ return ANSI_ESCAPE_PATTERN.sub("", choices[command].format_help()).rstrip()
27
+
28
+ msg = f"Command {command!r} is not registered with the argument parser."
29
+ raise ValueError(msg)
30
+
31
+
32
+ def render_reference() -> str:
33
+ """Render the complete command reference as MyST Markdown."""
34
+ parser = create_parser()
35
+ sections = [
36
+ "# Command reference",
37
+ "",
38
+ "This reference is generated from the CLI's `--help` output. Do not edit it manually.",
39
+ ]
40
+ for command in (None, *COMMAND_NAMES):
41
+ invocation = "mastodown" if command is None else f"mastodown {command}"
42
+ sections.extend(
43
+ [
44
+ "",
45
+ f"## `{invocation}`",
46
+ "",
47
+ "```console",
48
+ f"$ {invocation} --help",
49
+ format_command_help(parser, command),
50
+ "```",
51
+ ]
52
+ )
53
+ return "\n".join(sections) + "\n"
54
+
55
+
56
+ def write_reference(*, check: bool = False) -> None:
57
+ """Write the reference, or fail when the committed version is stale."""
58
+ content = render_reference()
59
+ if check:
60
+ if not REFERENCE_PATH.is_file() or REFERENCE_PATH.read_text(
61
+ encoding="utf-8"
62
+ ) != content:
63
+ msg = f"{REFERENCE_PATH.relative_to(PROJECT_DIR)} is out of date."
64
+ raise SystemExit(msg)
65
+ return
66
+
67
+ REFERENCE_PATH.write_text(content, encoding="utf-8")
68
+
69
+
70
+ def main() -> None:
71
+ """Generate the reference, accepting a freshness-check mode."""
72
+ write_reference(check="--check" in sys.argv[1:])
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()