tdemocracy 0.0.3__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, Jannis Necker
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,77 @@
1
+ Metadata-Version: 2.3
2
+ Name: tdemocracy
3
+ Version: 0.0.3
4
+ Summary: Listen to nuclear alerts from the Vera Rubin Observatory for the TDEmocracy project
5
+ Author: Jannis Necker
6
+ Author-email: necker@strw.leidenuniv.nl
7
+ Requires-Python: >=3.11,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Provides-Extra: dev
13
+ Provides-Extra: docs
14
+ Requires-Dist: autodoc-pydantic[erdantic] (>=2.2.0,<3.0.0) ; extra == "docs"
15
+ Requires-Dist: hop-client (>=0.12.1,<0.13.0)
16
+ Requires-Dist: mypy (>=1.5.1) ; extra == "dev"
17
+ Requires-Dist: pre-commit (>=3.4.0) ; extra == "dev"
18
+ Requires-Dist: py-avro-schema (>=3.8.2,<4.0.0)
19
+ Requires-Dist: pydantic (>=2.13.4,<3.0.0)
20
+ Requires-Dist: pydantic-settings (>=2.14.2,<3.0.0)
21
+ Requires-Dist: pytest (>=7.2) ; extra == "dev"
22
+ Requires-Dist: pytest-cov (>=4.0) ; extra == "dev"
23
+ Requires-Dist: sphinx (>=7.0) ; extra == "docs"
24
+ Requires-Dist: sphinx-autodoc-typehints (>=1.25) ; extra == "docs"
25
+ Requires-Dist: sphinx-rtd-theme (>=3.1.0,<4.0.0) ; extra == "docs"
26
+ Requires-Dist: types-pyyaml (>=6.0.12.20260518,<7.0.0.0) ; extra == "dev"
27
+ Project-URL: Documentation, https://JannisNe.github.io/tdemocracy/
28
+ Project-URL: Repository, https://github.com/JannisNe/tdemocracy
29
+ Description-Content-Type: text/markdown
30
+
31
+ [![Release](https://img.shields.io/github/v/release/JannisNe/tdemocracy)](https://img.shields.io/github/v/release/JannisNe/tdemocracy)
32
+ [![Build status](https://img.shields.io/github/actions/workflow/status/JannisNe/tdemocracy/main.yml?branch=main)](https://github.com/JannisNe/tdemocracy/actions/workflows/main.yml?query=branch%3Amain)
33
+ [![codecov](https://codecov.io/gh/JannisNe/tdemocracy/branch/main/graph/badge.svg)](https://codecov.io/gh/JannisNe/tdemocracy)
34
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/JannisNe/tdemocracy)](https://img.shields.io/github/commit-activity/m/JannisNe/tdemocracy)
35
+ [![License](https://img.shields.io/github/license/JannisNe/tdemocracy)](https://img.shields.io/github/license/JannisNe/tdemocracy)
36
+ [![Deploy Docs](https://github.com/JannisNe/tdemocracy/actions/workflows/deploy-docs.yml/badge.svg)](https://jannisne.github.io/tdemocracy)
37
+
38
+ # Listen to TDEmocracy!
39
+
40
+ This repository contains the data model used in the stream of candidate nuclear transients for the TDEmocracy project.
41
+
42
+ ## Prerequisites
43
+
44
+ You need an account at the [SCIMMA Hopskotch service](https://scimma.org/hopskotch). Please contact the TDEmocracy lead to have your account added to the Ampel-TDEmocracy group. You can store your username and password in your environment variables `TDEMOCRACY_USERNAME` and `TDEMOCRACY_PASSWORD` or in a `.env` file in the root of the repository.
45
+
46
+ ## Installation
47
+
48
+ Pull the repository and install the dependencies with [`poetry`](https://python-poetry.org/):
49
+
50
+ ```bash
51
+ git clone https://github.com/JannisNe/tdemocracy.git
52
+ cd tdemocracy
53
+ poetry install
54
+ ```
55
+
56
+ In the future, you will be able to install the package via `pip`:
57
+
58
+ ```bash
59
+ pip install tdemocracy
60
+ ```
61
+
62
+ ## Usage:
63
+
64
+ ```python
65
+ from tdemocracy.listen import listen_to_nuclear_stream
66
+
67
+ for report in listen_to_nuclear_stream():
68
+ # do something with the report
69
+ ...
70
+ ```
71
+
72
+ Find more detailed documentation [here](https://jannisne.github.io/tdemocracy).
73
+
74
+ ---
75
+
76
+ Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).
77
+
@@ -0,0 +1,46 @@
1
+ [![Release](https://img.shields.io/github/v/release/JannisNe/tdemocracy)](https://img.shields.io/github/v/release/JannisNe/tdemocracy)
2
+ [![Build status](https://img.shields.io/github/actions/workflow/status/JannisNe/tdemocracy/main.yml?branch=main)](https://github.com/JannisNe/tdemocracy/actions/workflows/main.yml?query=branch%3Amain)
3
+ [![codecov](https://codecov.io/gh/JannisNe/tdemocracy/branch/main/graph/badge.svg)](https://codecov.io/gh/JannisNe/tdemocracy)
4
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/JannisNe/tdemocracy)](https://img.shields.io/github/commit-activity/m/JannisNe/tdemocracy)
5
+ [![License](https://img.shields.io/github/license/JannisNe/tdemocracy)](https://img.shields.io/github/license/JannisNe/tdemocracy)
6
+ [![Deploy Docs](https://github.com/JannisNe/tdemocracy/actions/workflows/deploy-docs.yml/badge.svg)](https://jannisne.github.io/tdemocracy)
7
+
8
+ # Listen to TDEmocracy!
9
+
10
+ This repository contains the data model used in the stream of candidate nuclear transients for the TDEmocracy project.
11
+
12
+ ## Prerequisites
13
+
14
+ You need an account at the [SCIMMA Hopskotch service](https://scimma.org/hopskotch). Please contact the TDEmocracy lead to have your account added to the Ampel-TDEmocracy group. You can store your username and password in your environment variables `TDEMOCRACY_USERNAME` and `TDEMOCRACY_PASSWORD` or in a `.env` file in the root of the repository.
15
+
16
+ ## Installation
17
+
18
+ Pull the repository and install the dependencies with [`poetry`](https://python-poetry.org/):
19
+
20
+ ```bash
21
+ git clone https://github.com/JannisNe/tdemocracy.git
22
+ cd tdemocracy
23
+ poetry install
24
+ ```
25
+
26
+ In the future, you will be able to install the package via `pip`:
27
+
28
+ ```bash
29
+ pip install tdemocracy
30
+ ```
31
+
32
+ ## Usage:
33
+
34
+ ```python
35
+ from tdemocracy.listen import listen_to_nuclear_stream
36
+
37
+ for report in listen_to_nuclear_stream():
38
+ # do something with the report
39
+ ...
40
+ ```
41
+
42
+ Find more detailed documentation [here](https://jannisne.github.io/tdemocracy).
43
+
44
+ ---
45
+
46
+ Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).
@@ -0,0 +1,126 @@
1
+ [project]
2
+ name = "tdemocracy"
3
+ version = "0.0.3"
4
+ description = "Listen to nuclear alerts from the Vera Rubin Observatory for the TDEmocracy project"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11,<4.0"
7
+ authors = [
8
+ { name = "Jannis Necker", email = "necker@strw.leidenuniv.nl" },
9
+ ]
10
+ dependencies = [
11
+ "hop-client (>=0.12.1,<0.13.0)",
12
+ "py-avro-schema (>=3.8.2,<4.0.0)",
13
+ "pydantic (>=2.13.4,<3.0.0)",
14
+ "pydantic-settings (>=2.14.2,<3.0.0)",
15
+
16
+ ]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/JannisNe/tdemocracy"
20
+ Documentation = "https://JannisNe.github.io/tdemocracy/"
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=7.2",
25
+ "pytest-cov>=4.0",
26
+ "mypy>=1.5.1",
27
+ "pre-commit>=3.4.0",
28
+ "types-pyyaml (>=6.0.12.20260518,<7.0.0.0)",
29
+ ]
30
+ docs = [
31
+ "sphinx>=7.0",
32
+ "sphinx-autodoc-typehints>=1.25",
33
+ "autodoc-pydantic[erdantic] (>=2.2.0,<3.0.0)",
34
+ "sphinx-rtd-theme (>=3.1.0,<4.0.0)",
35
+ ]
36
+
37
+ [build-system]
38
+ requires = ["poetry-core>=2.0"]
39
+ build-backend = "poetry.core.masonry.api"
40
+
41
+ [tool.mypy]
42
+ files = ["tdemocracy"]
43
+ disallow_untyped_defs = true
44
+ disallow_any_unimported = true
45
+ no_implicit_optional = true
46
+ check_untyped_defs = true
47
+ warn_return_any = true
48
+ warn_unused_ignores = true
49
+ show_error_codes = true
50
+ plugins = [
51
+ "pydantic.mypy"
52
+ ]
53
+ [[tool.mypy.overrides]]
54
+ module = [
55
+ "hop.*",
56
+ ]
57
+ ignore_missing_imports = true
58
+
59
+ [tool.pytest.ini_options]
60
+ testpaths = ["tests"]
61
+
62
+ [tool.ruff]
63
+ target-version = "py313"
64
+ line-length = 120
65
+ fix = true
66
+ lint.select = [
67
+ # flake8-2020
68
+ "YTT",
69
+ # flake8-bandit
70
+ "S",
71
+ # flake8-bugbear
72
+ "B",
73
+ # flake8-builtins
74
+ "A",
75
+ # flake8-comprehensions
76
+ "C4",
77
+ # flake8-debugger
78
+ "T10",
79
+ # flake8-simplify
80
+ "SIM",
81
+ # isort
82
+ "I",
83
+ # mccabe
84
+ "C90",
85
+ # pycodestyle
86
+ "E",
87
+ "W",
88
+ # pyflakes
89
+ "F",
90
+ # pygrep-hooks
91
+ "PGH",
92
+ # pyupgrade
93
+ "UP",
94
+ # ruff
95
+ "RUF",
96
+ # tryceratops
97
+ "TRY",
98
+ ]
99
+ lint.ignore = [
100
+ # LineTooLong
101
+ "E501",
102
+ # DoNotAssignLambda
103
+ "E731",
104
+ ]
105
+
106
+ [tool.ruff.format]
107
+ preview = true
108
+
109
+ [tool.coverage.report]
110
+ skip_empty = true
111
+
112
+ [tool.coverage.run]
113
+ branch = true
114
+ source = ["tdemocracy"]
115
+
116
+
117
+ [tool.ruff.per-file-ignores]
118
+ "tests/*" = ["S101"]
119
+
120
+ [tool.poetry.group.docs]
121
+ optional = true
122
+
123
+ [tool.poetry.group.docs.dependencies]
124
+ sphinx = ">=7.0"
125
+ sphinx-autodoc-typehints = ">=1.25"
126
+ erdantic = ">=0.3.0"
@@ -0,0 +1 @@
1
+ __version__ = "0.0.3"
@@ -0,0 +1,49 @@
1
+ import logging
2
+ from collections.abc import Generator
3
+ from typing import Any
4
+
5
+ from hop import Stream
6
+ from hop.auth import Auth
7
+ from hop.io import StartPosition
8
+
9
+ from tdemocracy import __version__ as tdemocracy_version
10
+ from tdemocracy.model import NuclearTransientReport
11
+ from tdemocracy.settings import Settings
12
+
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ def listen_to_nuclear_stream(
17
+ start_at: Any = StartPosition.EARLIEST,
18
+ until_eos: bool = False,
19
+ settings: Settings | None = None,
20
+ skip_other_versions: bool = False,
21
+ ) -> Generator[NuclearTransientReport]:
22
+ """
23
+ Listen to Nuclear stream
24
+
25
+ :param start_at: where to start the stream, either of StartPosition.EARLIEST or StartPosition.LATEST
26
+ :type start_at: Any
27
+ :param until_eos: Stop loop when the end of the stream is reached or wait for next message (default)
28
+ :type until_eos: bool
29
+ :param settings: Settings to use, if not passed reads the corresponding from the environment or a `.env` file. See :class:`tdemocracy.settings.Settings` for details.
30
+ :type settings: Settings | None
31
+ :param skip_other_versions: Skip alerts with wrong versions (default)
32
+ :type skip_other_versions: bool
33
+ """
34
+ _settings = settings or Settings()
35
+ auth = Auth(_settings.username, _settings.password)
36
+ stream = Stream(auth=auth, start_at=start_at, until_eos=until_eos)
37
+
38
+ with stream.open(f"kafka://kafka.scimma.org/{_settings.topic}", mode="r", group_id=_settings.group_id) as s:
39
+ LOGGER.info(f"Listening to {_settings.topic}...")
40
+ for message in s:
41
+ LOGGER.debug("Received message")
42
+ if ((mv := message.content.get("model_version")) is None) or (mv != tdemocracy_version):
43
+ if skip_other_versions:
44
+ LOGGER.debug(f"Skipping version {mv}")
45
+ continue
46
+ else:
47
+ msg = f"Received message with wrong model_version: {mv}, expected: {tdemocracy_version}"
48
+ raise ValueError(msg)
49
+ yield NuclearTransientReport.model_validate(message.content)
@@ -0,0 +1,127 @@
1
+ from collections.abc import Sequence
2
+ from typing import Literal
3
+
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ class PhotometricPoint(BaseModel):
8
+ """
9
+ Observed photometric point in an LSST alert report.
10
+ Forced photometry (diaForcedSource) takes preference over alert photometry (diaSource).
11
+ """
12
+
13
+ id: int
14
+ """diaSourceId or diaForcedSourceId"""
15
+ source: Literal["LSST_DP", "LSST_FP"]
16
+ """id source"""
17
+ time: float
18
+ """midpointMjdTai"""
19
+ flux: float
20
+ """psfFlux"""
21
+ fluxerr: float
22
+ """psfFluxErr"""
23
+ band: str
24
+ """band"""
25
+ zp: float
26
+ """Photometric zero point"""
27
+ zpsys: str
28
+ """Zero point system"""
29
+
30
+
31
+ class Object(BaseModel):
32
+ """Object associated with an LSST alert report (diaObject)"""
33
+
34
+ id: int
35
+ """diaObjectId"""
36
+ external_id: str | None = None
37
+ """External Object ID"""
38
+ ra: float
39
+ """Right ascension (ra; deg)"""
40
+ ra_err: float | None = None
41
+ """Right ascension uncertainty (raErr; deg)"""
42
+ dec: float
43
+ """Declination (dec; deg)"""
44
+ dec_err: float | None = None
45
+ """Declination uncertainty (decErr; deg)"""
46
+ ra_dec_cov: float | None = None
47
+ """Right ascension/declination covariance (ra_dec_Doc; deg2)"""
48
+ source: str
49
+ """Data source"""
50
+
51
+
52
+ class Host(BaseModel):
53
+ """Host identified through catalog matching"""
54
+
55
+ name: str | None
56
+ """Name of the identifying ampel unit"""
57
+ source: str | list[str]
58
+ """Names of the catalogs containing this host"""
59
+ redshift: float | None
60
+ """Mean of the best redshift category for all catalog sources containing redshifts (see https://github.com/AmpelAstro/Ampel-HU-astro/blob/main/ampel/contrib/hu/t2/T2DigestRedshifts.py)"""
61
+ redshift_error: float | None = None
62
+ """Precision of the redshift category"""
63
+ distance: float
64
+ """Distance in arcsec"""
65
+ info: str | dict[str, dict[str, str | int]] | None = None
66
+ """Type info if included in the matching catalogs, catalog name -> type key -> type value"""
67
+
68
+
69
+ class MeanPosition(BaseModel):
70
+ """
71
+ Mean position of the transient calculated as the
72
+ weighted sum of the diaSource positions contributing to the diaSource.
73
+ """
74
+
75
+ mean_ra: float
76
+ """The mean RA (deg)"""
77
+ mean_dec: float
78
+ """The mean Dec (deg)"""
79
+ circularized_error: float
80
+ """Geometric mean of sqrt(raErr^2 + decErr^2) in arcsec"""
81
+ std: float
82
+ """Standard deviation of the datapoint distance to the mean position in arcsec"""
83
+
84
+
85
+ class TemplateFlux(BaseModel):
86
+ """Median and 90th percentile of the templateFlux of all diaSources
87
+ contributing to the diaObject in a single band."""
88
+
89
+ band: str
90
+ """Band name"""
91
+ median: float
92
+ """Median template flux"""
93
+ perc5: float
94
+ """5th percentile template flux"""
95
+ perc95: float
96
+ """95th percentile template flux"""
97
+
98
+
99
+ class TemplateFluxes(BaseModel):
100
+ """Median of the templateFlux of all diaSources contributing to the diaObject."""
101
+
102
+ u: TemplateFlux | None
103
+ g: TemplateFlux | None
104
+ r: TemplateFlux | None
105
+ i: TemplateFlux | None
106
+ z: TemplateFlux | None
107
+ y: TemplateFlux | None
108
+
109
+
110
+ class NuclearTransientReport(BaseModel):
111
+ """
112
+ Data model for LSST alert reports from Ampel.
113
+ """
114
+
115
+ version: str
116
+ """Version of the TDEmocracy nuclear filter"""
117
+ model_version: str
118
+ """Version of the TDEmocracy nuclear filter data model"""
119
+ object: Object
120
+ state: int
121
+ """unique identifier for underlying collection of data points in AMPEL"""
122
+ photometry: Sequence[PhotometricPoint]
123
+ host: Host | None
124
+ mean_position: MeanPosition
125
+ template_fluxes: TemplateFluxes
126
+
127
+ model_config = ConfigDict(extra="forbid")
@@ -0,0 +1,17 @@
1
+ from typing import Any
2
+
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+
5
+
6
+ class Settings(BaseSettings):
7
+ """Settings for tdemocracy. Per default, the values are being read from the environment as `TDEMOCRACY_<key>`, or the `tdemocracy.env` file."""
8
+
9
+ username: str
10
+ """Username for the Hopskotch stream"""
11
+ password: str
12
+ """Password for the Hopskotch stream"""
13
+ topic: str = "Ampel-TDEmocracy.nucelar-stream-dev"
14
+ """Hopskotch topic"""
15
+ group_id: Any | None = None
16
+ """Group ID when listening to the kafka stream. If None (default) a new value will be assigned with each call (good for testing). Set to a persistent <username>-<key> value to keep track of the stream position between calls (good for production)."""
17
+ model_config = SettingsConfigDict(env_prefix="TDEMOCRACY_", env_file="tdemocracy.env")