tns-mirror-client 1.0.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.
- tns_mirror_client-1.0.0/.gitignore +36 -0
- tns_mirror_client-1.0.0/PKG-INFO +144 -0
- tns_mirror_client-1.0.0/README.md +123 -0
- tns_mirror_client-1.0.0/pyproject.toml +69 -0
- tns_mirror_client-1.0.0/src/tns_mirror_client/__init__.py +38 -0
- tns_mirror_client-1.0.0/src/tns_mirror_client/client.py +353 -0
- tns_mirror_client-1.0.0/src/tns_mirror_client/errors.py +31 -0
- tns_mirror_client-1.0.0/src/tns_mirror_client/geometry.py +119 -0
- tns_mirror_client-1.0.0/src/tns_mirror_client/models.py +96 -0
- tns_mirror_client-1.0.0/tests/__init__.py +0 -0
- tns_mirror_client-1.0.0/tests/test_client.py +352 -0
- tns_mirror_client-1.0.0/tests/test_contract.py +128 -0
- tns_mirror_client-1.0.0/tests/test_geometry.py +211 -0
- tns_mirror_client-1.0.0/uv.lock +358 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
|
|
10
|
+
# Test / tooling
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
coverage.xml
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Secrets and local config — never commit a real TNS marker, bot api_key, or DSN
|
|
18
|
+
.env
|
|
19
|
+
.env.*
|
|
20
|
+
!.env.example
|
|
21
|
+
config/tns-mirror.yaml
|
|
22
|
+
*.local.yaml
|
|
23
|
+
|
|
24
|
+
# Documentation site
|
|
25
|
+
node_modules/
|
|
26
|
+
docs/_site/
|
|
27
|
+
.eleventy-cache/
|
|
28
|
+
|
|
29
|
+
# Downloaded catalogue working files (transient; TNS_WORKDIR)
|
|
30
|
+
/var/
|
|
31
|
+
work/
|
|
32
|
+
*.csv
|
|
33
|
+
*.csv.zip
|
|
34
|
+
*.tmp
|
|
35
|
+
|
|
36
|
+
design-tdd.md
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tns-mirror-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Typed, read-only Python client for a tns-mirror TNS catalogue database.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sarhatabaot/tns-mirror
|
|
6
|
+
Project-URL: Documentation, https://github.com/sarhatabaot/tns-mirror/tree/main/client
|
|
7
|
+
Project-URL: Changelog, https://github.com/sarhatabaot/tns-mirror/blob/main/CHANGELOG.md
|
|
8
|
+
Author: tns-mirror contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: astronomy,catalogue,cone-search,tns,transients
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# tns-mirror-client
|
|
23
|
+
|
|
24
|
+
Typed, read-only Python access to a [tns-mirror](https://github.com/sarhatabaot/tns-mirror)
|
|
25
|
+
database — a local mirror of the [IAU Transient Name Server](https://www.wis-tns.org)
|
|
26
|
+
public objects catalogue.
|
|
27
|
+
|
|
28
|
+
Add the library, get results. No hand-written SQL, and no rate-limited API on
|
|
29
|
+
your hot path.
|
|
30
|
+
|
|
31
|
+
```console
|
|
32
|
+
$ pip install 'tns-mirror-client>=1,<2'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import os
|
|
37
|
+
from tns_mirror_client import TnsMirror
|
|
38
|
+
|
|
39
|
+
with TnsMirror(dsn=os.environ["TNS_RO_DSN"]) as tns:
|
|
40
|
+
hit = tns.nearest(ra=203.1, dec=10.2, radius_arcsec=3.0)
|
|
41
|
+
if hit:
|
|
42
|
+
print(hit.name, hit.type, hit.redshift, f'{hit.separation_arcsec:.2f}"')
|
|
43
|
+
|
|
44
|
+
obj = tns.by_name("AT2026abc")
|
|
45
|
+
print(tns.count(), "objects mirrored")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You need a read-only DSN from whoever operates the mirror. This library never
|
|
49
|
+
writes, and it carries no TNS credentials — those belong to the server.
|
|
50
|
+
|
|
51
|
+
## Why not just write the SQL?
|
|
52
|
+
|
|
53
|
+
Because a cone search on a sphere has three edges that are easy to get wrong and
|
|
54
|
+
fail *silently*:
|
|
55
|
+
|
|
56
|
+
- **The cosine must be clamped** before `acos`, or an exact positional match —
|
|
57
|
+
the most common case in a cross-match — raises a domain error.
|
|
58
|
+
- **Near a pole** the RA bound stops meaning anything: two objects 177° apart in
|
|
59
|
+
right ascension can be 0.2° apart on the sky.
|
|
60
|
+
- **At the 0/360 seam**, `ra BETWEEN 359.5 AND 0.5` matches nothing at all.
|
|
61
|
+
|
|
62
|
+
This library handles all three, and keeps the indexed bounding-box prefilter that
|
|
63
|
+
makes the query fast. The geometry is tested by sampling the rim of the cone at
|
|
64
|
+
every bearing and asserting the prefilter never excludes a point that is
|
|
65
|
+
genuinely inside the radius.
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
| Method | Returns |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `nearest(ra, dec, radius_arcsec=3.0)` | `TnsObject \| None` — closest match, ties broken on `objid` |
|
|
72
|
+
| `search(ra, dec, radius_arcsec, limit=None)` | `list[TnsObject]` — everything in range, nearest first |
|
|
73
|
+
| `by_name(name)` | `TnsObject \| None` |
|
|
74
|
+
| `by_objid(objid)` | `TnsObject \| None` |
|
|
75
|
+
| `by_names(names)` | `dict[str, TnsObject]` — many in one round trip |
|
|
76
|
+
| `count()` | `int` |
|
|
77
|
+
| `meta()` | `MirrorMeta` — schema version and freshness |
|
|
78
|
+
|
|
79
|
+
`TnsObject` mirrors the schema columns exactly: `objid`, `name`, `ra`, `dec`,
|
|
80
|
+
`type`, `redshift`, `discoverydate`, `discoverymag`, `internal_names`,
|
|
81
|
+
`reporting_group`, `source_snapshot`, `refreshed_at` — plus `separation_arcsec`,
|
|
82
|
+
populated only by cone queries.
|
|
83
|
+
|
|
84
|
+
Everything but `objid`, `name`, `ra`, `dec`, `source_snapshot` and `refreshed_at`
|
|
85
|
+
is nullable, and a minimal TNS row is common. Do not assume `type` or `redshift`
|
|
86
|
+
is populated; `hit.is_classified` says whether TNS has published a
|
|
87
|
+
classification.
|
|
88
|
+
|
|
89
|
+
## Check freshness before you trust it
|
|
90
|
+
|
|
91
|
+
A row count looks identical whether the mirror synced an hour ago or died three
|
|
92
|
+
weeks ago:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
meta = tns.meta()
|
|
96
|
+
if not meta.is_fresh(max_age_hours=26):
|
|
97
|
+
raise RuntimeError(f"TNS mirror is stale: last sync {meta.age} ago")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Versioning
|
|
101
|
+
|
|
102
|
+
**Client 1.x speaks schema v1.** The major version tracks the *contract*, not the
|
|
103
|
+
project milestone, so pin it:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
tns-mirror-client>=1,<2
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
You are then insulated from additive schema changes — a new nullable column or a
|
|
110
|
+
new index bumps the minor and never breaks a reader. A breaking change bumps both
|
|
111
|
+
majors together and is announced in the changelog.
|
|
112
|
+
|
|
113
|
+
The client reads the mirror's published `schema_version` on connect and raises
|
|
114
|
+
`SchemaVersionError` on a mismatch rather than returning quietly wrong answers.
|
|
115
|
+
That check needs `SELECT` on `tns_mirror_meta`, which the mirror's
|
|
116
|
+
`print-grants` output includes; pass `check_schema_version=False` to skip it.
|
|
117
|
+
|
|
118
|
+
## Connections
|
|
119
|
+
|
|
120
|
+
`TnsMirror(dsn=...)` opens lazily and closes with the context manager or
|
|
121
|
+
`.close()`. To reuse a connection you already manage — from a pool, say — pass it
|
|
122
|
+
instead, and the client will not close what it did not open:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
tns = TnsMirror(connection=my_conn)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If the mirror was deployed with a non-default `TNS_SCHEMA`/`TNS_TABLE`, pass
|
|
129
|
+
`schema=` and `table=`.
|
|
130
|
+
|
|
131
|
+
The client sets its session read-only as defence in depth, so even a bug in this
|
|
132
|
+
library cannot write through a privileged role.
|
|
133
|
+
|
|
134
|
+
## Dependencies
|
|
135
|
+
|
|
136
|
+
One: `psycopg`. This gets embedded in other people's applications, so results are
|
|
137
|
+
plain dataclasses rather than a validation framework.
|
|
138
|
+
|
|
139
|
+
Requires Python 3.11+.
|
|
140
|
+
|
|
141
|
+
## Licence
|
|
142
|
+
|
|
143
|
+
MIT. The mirrored catalogue itself is not the mirror's to license — follow the
|
|
144
|
+
TNS data-use terms and cite TNS as they require.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# tns-mirror-client
|
|
2
|
+
|
|
3
|
+
Typed, read-only Python access to a [tns-mirror](https://github.com/sarhatabaot/tns-mirror)
|
|
4
|
+
database — a local mirror of the [IAU Transient Name Server](https://www.wis-tns.org)
|
|
5
|
+
public objects catalogue.
|
|
6
|
+
|
|
7
|
+
Add the library, get results. No hand-written SQL, and no rate-limited API on
|
|
8
|
+
your hot path.
|
|
9
|
+
|
|
10
|
+
```console
|
|
11
|
+
$ pip install 'tns-mirror-client>=1,<2'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import os
|
|
16
|
+
from tns_mirror_client import TnsMirror
|
|
17
|
+
|
|
18
|
+
with TnsMirror(dsn=os.environ["TNS_RO_DSN"]) as tns:
|
|
19
|
+
hit = tns.nearest(ra=203.1, dec=10.2, radius_arcsec=3.0)
|
|
20
|
+
if hit:
|
|
21
|
+
print(hit.name, hit.type, hit.redshift, f'{hit.separation_arcsec:.2f}"')
|
|
22
|
+
|
|
23
|
+
obj = tns.by_name("AT2026abc")
|
|
24
|
+
print(tns.count(), "objects mirrored")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You need a read-only DSN from whoever operates the mirror. This library never
|
|
28
|
+
writes, and it carries no TNS credentials — those belong to the server.
|
|
29
|
+
|
|
30
|
+
## Why not just write the SQL?
|
|
31
|
+
|
|
32
|
+
Because a cone search on a sphere has three edges that are easy to get wrong and
|
|
33
|
+
fail *silently*:
|
|
34
|
+
|
|
35
|
+
- **The cosine must be clamped** before `acos`, or an exact positional match —
|
|
36
|
+
the most common case in a cross-match — raises a domain error.
|
|
37
|
+
- **Near a pole** the RA bound stops meaning anything: two objects 177° apart in
|
|
38
|
+
right ascension can be 0.2° apart on the sky.
|
|
39
|
+
- **At the 0/360 seam**, `ra BETWEEN 359.5 AND 0.5` matches nothing at all.
|
|
40
|
+
|
|
41
|
+
This library handles all three, and keeps the indexed bounding-box prefilter that
|
|
42
|
+
makes the query fast. The geometry is tested by sampling the rim of the cone at
|
|
43
|
+
every bearing and asserting the prefilter never excludes a point that is
|
|
44
|
+
genuinely inside the radius.
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
| Method | Returns |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `nearest(ra, dec, radius_arcsec=3.0)` | `TnsObject \| None` — closest match, ties broken on `objid` |
|
|
51
|
+
| `search(ra, dec, radius_arcsec, limit=None)` | `list[TnsObject]` — everything in range, nearest first |
|
|
52
|
+
| `by_name(name)` | `TnsObject \| None` |
|
|
53
|
+
| `by_objid(objid)` | `TnsObject \| None` |
|
|
54
|
+
| `by_names(names)` | `dict[str, TnsObject]` — many in one round trip |
|
|
55
|
+
| `count()` | `int` |
|
|
56
|
+
| `meta()` | `MirrorMeta` — schema version and freshness |
|
|
57
|
+
|
|
58
|
+
`TnsObject` mirrors the schema columns exactly: `objid`, `name`, `ra`, `dec`,
|
|
59
|
+
`type`, `redshift`, `discoverydate`, `discoverymag`, `internal_names`,
|
|
60
|
+
`reporting_group`, `source_snapshot`, `refreshed_at` — plus `separation_arcsec`,
|
|
61
|
+
populated only by cone queries.
|
|
62
|
+
|
|
63
|
+
Everything but `objid`, `name`, `ra`, `dec`, `source_snapshot` and `refreshed_at`
|
|
64
|
+
is nullable, and a minimal TNS row is common. Do not assume `type` or `redshift`
|
|
65
|
+
is populated; `hit.is_classified` says whether TNS has published a
|
|
66
|
+
classification.
|
|
67
|
+
|
|
68
|
+
## Check freshness before you trust it
|
|
69
|
+
|
|
70
|
+
A row count looks identical whether the mirror synced an hour ago or died three
|
|
71
|
+
weeks ago:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
meta = tns.meta()
|
|
75
|
+
if not meta.is_fresh(max_age_hours=26):
|
|
76
|
+
raise RuntimeError(f"TNS mirror is stale: last sync {meta.age} ago")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Versioning
|
|
80
|
+
|
|
81
|
+
**Client 1.x speaks schema v1.** The major version tracks the *contract*, not the
|
|
82
|
+
project milestone, so pin it:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
tns-mirror-client>=1,<2
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You are then insulated from additive schema changes — a new nullable column or a
|
|
89
|
+
new index bumps the minor and never breaks a reader. A breaking change bumps both
|
|
90
|
+
majors together and is announced in the changelog.
|
|
91
|
+
|
|
92
|
+
The client reads the mirror's published `schema_version` on connect and raises
|
|
93
|
+
`SchemaVersionError` on a mismatch rather than returning quietly wrong answers.
|
|
94
|
+
That check needs `SELECT` on `tns_mirror_meta`, which the mirror's
|
|
95
|
+
`print-grants` output includes; pass `check_schema_version=False` to skip it.
|
|
96
|
+
|
|
97
|
+
## Connections
|
|
98
|
+
|
|
99
|
+
`TnsMirror(dsn=...)` opens lazily and closes with the context manager or
|
|
100
|
+
`.close()`. To reuse a connection you already manage — from a pool, say — pass it
|
|
101
|
+
instead, and the client will not close what it did not open:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
tns = TnsMirror(connection=my_conn)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If the mirror was deployed with a non-default `TNS_SCHEMA`/`TNS_TABLE`, pass
|
|
108
|
+
`schema=` and `table=`.
|
|
109
|
+
|
|
110
|
+
The client sets its session read-only as defence in depth, so even a bug in this
|
|
111
|
+
library cannot write through a privileged role.
|
|
112
|
+
|
|
113
|
+
## Dependencies
|
|
114
|
+
|
|
115
|
+
One: `psycopg`. This gets embedded in other people's applications, so results are
|
|
116
|
+
plain dataclasses rather than a validation framework.
|
|
117
|
+
|
|
118
|
+
Requires Python 3.11+.
|
|
119
|
+
|
|
120
|
+
## Licence
|
|
121
|
+
|
|
122
|
+
MIT. The mirrored catalogue itself is not the mirror's to license — follow the
|
|
123
|
+
TNS data-use terms and cite TNS as they require.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tns-mirror-client"
|
|
7
|
+
# 1.x speaks schema v1 (see schema/README.md). The major tracks the contract,
|
|
8
|
+
# not the project milestone, so consumers can pin 'tns-mirror-client>=1,<2'.
|
|
9
|
+
version = "1.0.0"
|
|
10
|
+
description = "Typed, read-only Python client for a tns-mirror TNS catalogue database."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
# Deliberately wider than the server's floor: this is embedded in other people's
|
|
13
|
+
# applications, and a client that forces a Python upgrade is a client nobody adopts.
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
license = "MIT"
|
|
16
|
+
authors = [{ name = "tns-mirror contributors" }]
|
|
17
|
+
keywords = ["astronomy", "tns", "transients", "catalogue", "cone-search"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Astronomy",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
# One dependency. This library gets embedded in other people's applications, so
|
|
29
|
+
# every dependency it carries becomes theirs — hence dataclasses, not pydantic.
|
|
30
|
+
dependencies = ["psycopg[binary]>=3.1"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/sarhatabaot/tns-mirror"
|
|
34
|
+
Documentation = "https://github.com/sarhatabaot/tns-mirror/tree/main/client"
|
|
35
|
+
Changelog = "https://github.com/sarhatabaot/tns-mirror/blob/main/CHANGELOG.md"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.3.3",
|
|
40
|
+
"pytest-cov>=5.0.0",
|
|
41
|
+
"ruff>=0.7.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/tns_mirror_client"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 96
|
|
49
|
+
target-version = "py311"
|
|
50
|
+
src = ["src", "tests"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "W", "I", "UP", "B", "S", "C4", "SIM", "RUF"]
|
|
54
|
+
ignore = [
|
|
55
|
+
# 'type' shadows a builtin, but it is a schema column name and the model must
|
|
56
|
+
# use the contract's vocabulary.
|
|
57
|
+
"A003",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint.per-file-ignores]
|
|
61
|
+
"tests/*" = ["S101", "S105", "S106", "SLF001", "S608"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
addopts = "-q --strict-markers"
|
|
66
|
+
markers = [
|
|
67
|
+
"integration: needs a live Postgres (TNS_TEST_DSN); skipped otherwise",
|
|
68
|
+
]
|
|
69
|
+
filterwarnings = ["error"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""tns-mirror-client — typed, read-only access to a tns-mirror database.
|
|
2
|
+
|
|
3
|
+
from tns_mirror_client import TnsMirror
|
|
4
|
+
|
|
5
|
+
with TnsMirror(dsn=os.environ["TNS_RO_DSN"]) as tns:
|
|
6
|
+
hit = tns.nearest(ra=203.1, dec=10.2, radius_arcsec=3.0)
|
|
7
|
+
if hit:
|
|
8
|
+
print(hit.name, hit.type, hit.redshift)
|
|
9
|
+
|
|
10
|
+
The library is the schema, packaged as a Python API. It reads; it never writes,
|
|
11
|
+
and it carries no TNS credentials — those belong to the server. Connect with the
|
|
12
|
+
read-only role the mirror's operator gives you.
|
|
13
|
+
|
|
14
|
+
**Versioning.** This is 1.x, and it speaks schema v1. Pin
|
|
15
|
+
``tns-mirror-client>=1,<2`` and you are insulated from additive schema changes;
|
|
16
|
+
a breaking one bumps both majors together. The client checks the mirror's
|
|
17
|
+
published schema version on connect and refuses to guess.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .client import SCHEMA_VERSION, TnsMirror
|
|
21
|
+
from .errors import MirrorUnavailable, SchemaVersionError, TnsMirrorClientError
|
|
22
|
+
from .geometry import angular_separation_deg, bounding_box
|
|
23
|
+
from .models import MirrorMeta, TnsObject
|
|
24
|
+
|
|
25
|
+
__version__ = "1.0.0"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"SCHEMA_VERSION",
|
|
29
|
+
"MirrorMeta",
|
|
30
|
+
"MirrorUnavailable",
|
|
31
|
+
"SchemaVersionError",
|
|
32
|
+
"TnsMirror",
|
|
33
|
+
"TnsMirrorClientError",
|
|
34
|
+
"TnsObject",
|
|
35
|
+
"__version__",
|
|
36
|
+
"angular_separation_deg",
|
|
37
|
+
"bounding_box",
|
|
38
|
+
]
|