lsst-felis 27.0.0rc3__py3-none-any.whl → 27.2024.1800__py3-none-any.whl
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.
Potentially problematic release.
This version of lsst-felis might be problematic. Click here for more details.
- felis/__init__.py +0 -25
- felis/cli.py +23 -195
- felis/datamodel.py +59 -43
- felis/db/sqltypes.py +3 -3
- felis/metadata.py +21 -12
- felis/tap.py +67 -80
- felis/validation.py +1 -1
- felis/version.py +1 -1
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/METADATA +5 -3
- lsst_felis-27.2024.1800.dist-info/RECORD +20 -0
- felis/check.py +0 -381
- felis/simple.py +0 -424
- felis/utils.py +0 -100
- felis/visitor.py +0 -180
- lsst_felis-27.0.0rc3.dist-info/RECORD +0 -24
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/COPYRIGHT +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/LICENSE +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/WHEEL +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/entry_points.txt +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/top_level.txt +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/zip-safe +0 -0
felis/tap.py
CHANGED
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
__all__ = ["Tap11Base", "TapLoadingVisitor", "init_tables"]
|
|
25
25
|
|
|
26
26
|
import logging
|
|
27
|
-
from collections.abc import Iterable,
|
|
27
|
+
from collections.abc import Iterable, MutableMapping
|
|
28
28
|
from typing import Any
|
|
29
29
|
|
|
30
30
|
from sqlalchemy import Column, Integer, String
|
|
@@ -34,14 +34,13 @@ from sqlalchemy.orm import Session, declarative_base, sessionmaker
|
|
|
34
34
|
from sqlalchemy.schema import MetaData
|
|
35
35
|
from sqlalchemy.sql.expression import Insert, insert
|
|
36
36
|
|
|
37
|
-
from
|
|
38
|
-
from .types import FelisType
|
|
39
|
-
from .visitor import Visitor
|
|
37
|
+
from felis import datamodel
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
from .datamodel import Constraint, Index, Schema, Table
|
|
40
|
+
from .types import FelisType
|
|
42
41
|
|
|
43
42
|
Tap11Base: Any = declarative_base() # Any to avoid mypy mess with SA 2
|
|
44
|
-
logger = logging.getLogger(
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
45
44
|
|
|
46
45
|
IDENTIFIER_LENGTH = 128
|
|
47
46
|
SMALL_FIELD_LENGTH = 32
|
|
@@ -133,7 +132,7 @@ def init_tables(
|
|
|
133
132
|
)
|
|
134
133
|
|
|
135
134
|
|
|
136
|
-
class TapLoadingVisitor
|
|
135
|
+
class TapLoadingVisitor:
|
|
137
136
|
"""Felis schema visitor for generating TAP schema.
|
|
138
137
|
|
|
139
138
|
Parameters
|
|
@@ -154,6 +153,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
154
153
|
catalog_name: str | None = None,
|
|
155
154
|
schema_name: str | None = None,
|
|
156
155
|
tap_tables: MutableMapping[str, Any] | None = None,
|
|
156
|
+
tap_schema_index: int | None = None,
|
|
157
157
|
):
|
|
158
158
|
self.graph_index: MutableMapping[str, Any] = {}
|
|
159
159
|
self.catalog_name = catalog_name
|
|
@@ -161,7 +161,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
161
161
|
self.engine = engine
|
|
162
162
|
self._mock_connection: MockConnection | None = None
|
|
163
163
|
self.tables = tap_tables or init_tables()
|
|
164
|
-
self.
|
|
164
|
+
self.tap_schema_index = tap_schema_index
|
|
165
165
|
|
|
166
166
|
@classmethod
|
|
167
167
|
def from_mock_connection(
|
|
@@ -170,30 +170,30 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
170
170
|
catalog_name: str | None = None,
|
|
171
171
|
schema_name: str | None = None,
|
|
172
172
|
tap_tables: MutableMapping[str, Any] | None = None,
|
|
173
|
+
tap_schema_index: int | None = None,
|
|
173
174
|
) -> TapLoadingVisitor:
|
|
174
175
|
visitor = cls(engine=None, catalog_name=catalog_name, schema_name=schema_name, tap_tables=tap_tables)
|
|
175
176
|
visitor._mock_connection = mock_connection
|
|
177
|
+
visitor.tap_schema_index = tap_schema_index
|
|
176
178
|
return visitor
|
|
177
179
|
|
|
178
|
-
def visit_schema(self, schema_obj:
|
|
179
|
-
self.checker.check_schema(schema_obj)
|
|
180
|
-
if (version_obj := schema_obj.get("version")) is not None:
|
|
181
|
-
self.visit_schema_version(version_obj, schema_obj)
|
|
180
|
+
def visit_schema(self, schema_obj: Schema) -> None:
|
|
182
181
|
schema = self.tables["schemas"]()
|
|
183
182
|
# Override with default
|
|
184
|
-
self.schema_name = self.schema_name or schema_obj
|
|
183
|
+
self.schema_name = self.schema_name or schema_obj.name
|
|
185
184
|
|
|
186
185
|
schema.schema_name = self._schema_name()
|
|
187
|
-
schema.description = schema_obj.
|
|
188
|
-
schema.utype = schema_obj.
|
|
189
|
-
schema.schema_index =
|
|
186
|
+
schema.description = schema_obj.description
|
|
187
|
+
schema.utype = schema_obj.votable_utype
|
|
188
|
+
schema.schema_index = self.tap_schema_index
|
|
189
|
+
logger.debug(f"Set TAP_SCHEMA index: {self.tap_schema_index}")
|
|
190
190
|
|
|
191
191
|
if self.engine is not None:
|
|
192
192
|
session: Session = sessionmaker(self.engine)()
|
|
193
193
|
|
|
194
194
|
session.add(schema)
|
|
195
195
|
|
|
196
|
-
for table_obj in schema_obj
|
|
196
|
+
for table_obj in schema_obj.tables:
|
|
197
197
|
table, columns = self.visit_table(table_obj, schema_obj)
|
|
198
198
|
session.add(table)
|
|
199
199
|
session.add_all(columns)
|
|
@@ -202,6 +202,8 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
202
202
|
session.add_all(keys)
|
|
203
203
|
session.add_all(key_columns)
|
|
204
204
|
|
|
205
|
+
logger.debug("Committing TAP schema: %s", schema_obj.name)
|
|
206
|
+
logger.debug("TAP tables: %s", len(self.tables))
|
|
205
207
|
session.commit()
|
|
206
208
|
else:
|
|
207
209
|
logger.info("Dry run, not inserting into database")
|
|
@@ -211,7 +213,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
211
213
|
conn = self._mock_connection
|
|
212
214
|
conn.execute(_insert(self.tables["schemas"], schema))
|
|
213
215
|
|
|
214
|
-
for table_obj in schema_obj
|
|
216
|
+
for table_obj in schema_obj.tables:
|
|
215
217
|
table, columns = self.visit_table(table_obj, schema_obj)
|
|
216
218
|
conn.execute(_insert(self.tables["tables"], table))
|
|
217
219
|
for column in columns:
|
|
@@ -223,56 +225,45 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
223
225
|
for key_column in key_columns:
|
|
224
226
|
conn.execute(_insert(self.tables["key_columns"], key_column))
|
|
225
227
|
|
|
226
|
-
def visit_constraints(self, schema_obj:
|
|
228
|
+
def visit_constraints(self, schema_obj: Schema) -> tuple:
|
|
227
229
|
all_keys = []
|
|
228
230
|
all_key_columns = []
|
|
229
|
-
for table_obj in schema_obj
|
|
230
|
-
for c in table_obj.
|
|
231
|
-
key, key_columns = self.visit_constraint(c
|
|
231
|
+
for table_obj in schema_obj.tables:
|
|
232
|
+
for c in table_obj.constraints:
|
|
233
|
+
key, key_columns = self.visit_constraint(c)
|
|
232
234
|
if not key:
|
|
233
235
|
continue
|
|
234
236
|
all_keys.append(key)
|
|
235
237
|
all_key_columns += key_columns
|
|
236
238
|
return all_keys, all_key_columns
|
|
237
239
|
|
|
238
|
-
def
|
|
239
|
-
|
|
240
|
-
) -> None:
|
|
241
|
-
# Docstring is inherited.
|
|
242
|
-
|
|
243
|
-
# For now we ignore schema versioning completely, still do some checks.
|
|
244
|
-
self.checker.check_schema_version(version_obj, schema_obj)
|
|
245
|
-
|
|
246
|
-
def visit_table(self, table_obj: _Mapping, schema_obj: _Mapping) -> tuple:
|
|
247
|
-
self.checker.check_table(table_obj, schema_obj)
|
|
248
|
-
table_id = table_obj["@id"]
|
|
240
|
+
def visit_table(self, table_obj: Table, schema_obj: Schema) -> tuple:
|
|
241
|
+
table_id = table_obj.id
|
|
249
242
|
table = self.tables["tables"]()
|
|
250
243
|
table.schema_name = self._schema_name()
|
|
251
|
-
table.table_name = self._table_name(table_obj
|
|
244
|
+
table.table_name = self._table_name(table_obj.name)
|
|
252
245
|
table.table_type = "table"
|
|
253
|
-
table.utype = table_obj.
|
|
254
|
-
table.description = table_obj.
|
|
255
|
-
table.table_index =
|
|
246
|
+
table.utype = table_obj.votable_utype
|
|
247
|
+
table.description = table_obj.description
|
|
248
|
+
table.table_index = 0 if table_obj.tap_table_index is None else table_obj.tap_table_index
|
|
256
249
|
|
|
257
|
-
columns = [self.visit_column(c, table_obj) for c in table_obj
|
|
258
|
-
self.visit_primary_key(table_obj.
|
|
250
|
+
columns = [self.visit_column(c, table_obj) for c in table_obj.columns]
|
|
251
|
+
self.visit_primary_key(table_obj.primary_key, table_obj)
|
|
259
252
|
|
|
260
|
-
for i in table_obj.
|
|
253
|
+
for i in table_obj.indexes:
|
|
261
254
|
self.visit_index(i, table)
|
|
262
255
|
|
|
263
256
|
self.graph_index[table_id] = table
|
|
264
257
|
return table, columns
|
|
265
258
|
|
|
266
|
-
def check_column(self, column_obj:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
datatype_name = column_obj.get("datatype", "")
|
|
271
|
-
felis_type = FelisType.felis_type(datatype_name)
|
|
259
|
+
def check_column(self, column_obj: datamodel.Column) -> None:
|
|
260
|
+
_id = column_obj.id
|
|
261
|
+
datatype_name = column_obj.datatype
|
|
262
|
+
felis_type = FelisType.felis_type(datatype_name.value)
|
|
272
263
|
if felis_type.is_sized:
|
|
273
264
|
# It is expected that both arraysize and length are fine for
|
|
274
265
|
# length types.
|
|
275
|
-
arraysize = column_obj.
|
|
266
|
+
arraysize = column_obj.votable_arraysize or column_obj.length
|
|
276
267
|
if arraysize is None:
|
|
277
268
|
logger.warning(
|
|
278
269
|
f"votable:arraysize and length for {_id} are None for type {datatype_name}. "
|
|
@@ -283,7 +274,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
283
274
|
# datetime types really should have a votable:arraysize, because
|
|
284
275
|
# they are converted to strings and the `length` is loosely to the
|
|
285
276
|
# string size
|
|
286
|
-
if
|
|
277
|
+
if not column_obj.votable_arraysize:
|
|
287
278
|
logger.warning(
|
|
288
279
|
f"votable:arraysize for {_id} is None for type {datatype_name}. "
|
|
289
280
|
f'Using length "*". '
|
|
@@ -291,47 +282,45 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
291
282
|
"materialized datetime/timestamp strings."
|
|
292
283
|
)
|
|
293
284
|
|
|
294
|
-
def visit_column(self, column_obj:
|
|
295
|
-
self.check_column(column_obj
|
|
296
|
-
column_id = column_obj
|
|
297
|
-
table_name = self._table_name(table_obj
|
|
285
|
+
def visit_column(self, column_obj: datamodel.Column, table_obj: Table) -> Tap11Base:
|
|
286
|
+
self.check_column(column_obj)
|
|
287
|
+
column_id = column_obj.id
|
|
288
|
+
table_name = self._table_name(table_obj.name)
|
|
298
289
|
|
|
299
290
|
column = self.tables["columns"]()
|
|
300
291
|
column.table_name = table_name
|
|
301
|
-
column.column_name = column_obj
|
|
292
|
+
column.column_name = column_obj.name
|
|
302
293
|
|
|
303
|
-
felis_datatype = column_obj
|
|
304
|
-
felis_type = FelisType.felis_type(felis_datatype)
|
|
305
|
-
column.datatype = column_obj.
|
|
294
|
+
felis_datatype = column_obj.datatype
|
|
295
|
+
felis_type = FelisType.felis_type(felis_datatype.value)
|
|
296
|
+
column.datatype = column_obj.votable_datatype or felis_type.votable_name
|
|
306
297
|
|
|
307
298
|
arraysize = None
|
|
308
299
|
if felis_type.is_sized:
|
|
309
|
-
|
|
310
|
-
arraysize = column_obj.get("votable:arraysize", column_obj.get("length", "*"))
|
|
300
|
+
arraysize = column_obj.votable_arraysize or column_obj.length or "*"
|
|
311
301
|
if felis_type.is_timestamp:
|
|
312
|
-
arraysize = column_obj.
|
|
302
|
+
arraysize = column_obj.votable_arraysize or "*"
|
|
313
303
|
column.arraysize = arraysize
|
|
314
304
|
|
|
315
|
-
column.xtype = column_obj.
|
|
316
|
-
column.description = column_obj.
|
|
317
|
-
column.utype = column_obj.
|
|
305
|
+
column.xtype = column_obj.votable_xtype
|
|
306
|
+
column.description = column_obj.description
|
|
307
|
+
column.utype = column_obj.votable_utype
|
|
318
308
|
|
|
319
|
-
unit = column_obj.
|
|
309
|
+
unit = column_obj.ivoa_unit or column_obj.fits_tunit
|
|
320
310
|
column.unit = unit
|
|
321
|
-
column.ucd = column_obj.
|
|
311
|
+
column.ucd = column_obj.ivoa_ucd
|
|
322
312
|
|
|
323
313
|
# We modify this after we process columns
|
|
324
314
|
column.indexed = 0
|
|
325
315
|
|
|
326
|
-
column.principal = column_obj.
|
|
327
|
-
column.std = column_obj.
|
|
328
|
-
column.column_index = column_obj.
|
|
316
|
+
column.principal = column_obj.tap_principal
|
|
317
|
+
column.std = column_obj.tap_std
|
|
318
|
+
column.column_index = column_obj.tap_column_index
|
|
329
319
|
|
|
330
320
|
self.graph_index[column_id] = column
|
|
331
321
|
return column
|
|
332
322
|
|
|
333
|
-
def visit_primary_key(self, primary_key_obj: str | Iterable[str], table_obj:
|
|
334
|
-
self.checker.check_primary_key(primary_key_obj, table_obj)
|
|
323
|
+
def visit_primary_key(self, primary_key_obj: str | Iterable[str] | None, table_obj: Table) -> None:
|
|
335
324
|
if primary_key_obj:
|
|
336
325
|
if isinstance(primary_key_obj, str):
|
|
337
326
|
primary_key_obj = [primary_key_obj]
|
|
@@ -341,19 +330,18 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
341
330
|
columns[0].indexed = 1
|
|
342
331
|
return None
|
|
343
332
|
|
|
344
|
-
def visit_constraint(self, constraint_obj:
|
|
345
|
-
|
|
346
|
-
constraint_type = constraint_obj["@type"]
|
|
333
|
+
def visit_constraint(self, constraint_obj: Constraint) -> tuple:
|
|
334
|
+
constraint_type = constraint_obj.type
|
|
347
335
|
key = None
|
|
348
336
|
key_columns = []
|
|
349
337
|
if constraint_type == "ForeignKey":
|
|
350
|
-
constraint_name = constraint_obj
|
|
351
|
-
description = constraint_obj.
|
|
352
|
-
utype = constraint_obj.
|
|
338
|
+
constraint_name = constraint_obj.name
|
|
339
|
+
description = constraint_obj.description
|
|
340
|
+
utype = constraint_obj.votable_utype
|
|
353
341
|
|
|
354
|
-
columns = [self.graph_index[
|
|
342
|
+
columns = [self.graph_index[col_id] for col_id in getattr(constraint_obj, "columns", [])]
|
|
355
343
|
refcolumns = [
|
|
356
|
-
self.graph_index[
|
|
344
|
+
self.graph_index[refcol_id] for refcol_id in getattr(constraint_obj, "referenced_columns", [])
|
|
357
345
|
]
|
|
358
346
|
|
|
359
347
|
table_name = None
|
|
@@ -386,9 +374,8 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
|
|
|
386
374
|
key_columns.append(key_column)
|
|
387
375
|
return key, key_columns
|
|
388
376
|
|
|
389
|
-
def visit_index(self, index_obj:
|
|
390
|
-
self.
|
|
391
|
-
columns = [self.graph_index[col["@id"]] for col in index_obj.get("columns", [])]
|
|
377
|
+
def visit_index(self, index_obj: Index, table_obj: Table) -> None:
|
|
378
|
+
columns = [self.graph_index[col_id] for col_id in getattr(index_obj, "columns", [])]
|
|
392
379
|
# if just one column and it's indexed, update the object
|
|
393
380
|
if len(columns) == 1:
|
|
394
381
|
columns[0].indexed = 1
|
felis/validation.py
CHANGED
|
@@ -60,7 +60,7 @@ class RspTable(Table):
|
|
|
60
60
|
|
|
61
61
|
@model_validator(mode="after") # type: ignore[arg-type]
|
|
62
62
|
@classmethod
|
|
63
|
-
def check_tap_principal(cls: Any, tbl:
|
|
63
|
+
def check_tap_principal(cls: Any, tbl: RspTable) -> RspTable:
|
|
64
64
|
"""Check that at least one column is flagged as 'principal' for
|
|
65
65
|
TAP purposes.
|
|
66
66
|
"""
|
felis/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "27.
|
|
2
|
+
__version__ = "27.2024.1800"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 27.
|
|
3
|
+
Version: 27.2024.1800
|
|
4
4
|
Summary: A vocabulary for describing catalogs and acting on those descriptions
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: GNU General Public License v3 or later (GPLv3+)
|
|
7
|
-
Project-URL: Homepage, https://
|
|
7
|
+
Project-URL: Homepage, https://felis.lsst.io
|
|
8
|
+
Project-URL: Source, https://github.com/lsst/felis
|
|
8
9
|
Keywords: lsst
|
|
9
10
|
Classifier: Intended Audience :: Science/Research
|
|
10
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -21,9 +22,10 @@ Requires-Dist: astropy >=4
|
|
|
21
22
|
Requires-Dist: sqlalchemy >=1.4
|
|
22
23
|
Requires-Dist: click >=7
|
|
23
24
|
Requires-Dist: pyyaml >=6
|
|
24
|
-
Requires-Dist: pyld >=2
|
|
25
25
|
Requires-Dist: pydantic <3,>=2
|
|
26
26
|
Requires-Dist: lsst-utils
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: documenteer[guide] ; extra == 'dev'
|
|
27
29
|
Provides-Extra: test
|
|
28
30
|
Requires-Dist: pytest >=3.2 ; extra == 'test'
|
|
29
31
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
felis/__init__.py,sha256=THmRg3ylB4E73XhFjJX7YlnV_CM3lr_gZO_HqQFzIQ4,937
|
|
2
|
+
felis/cli.py,sha256=l_4srdXPghBLAVuOvfJVdyIVhq45kTV5KYskmYSsIUA,10279
|
|
3
|
+
felis/datamodel.py,sha256=Jf6WQLoxuERw61GFF9Q1Vl2hSr3-osym_FtJNlOZGFU,20540
|
|
4
|
+
felis/metadata.py,sha256=R2mJgtupBDG_B7sPhV8NlAEa5XI6ezy8JhZjBT5Jme0,18907
|
|
5
|
+
felis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
felis/tap.py,sha256=fVYvvIFk_vccXqbcFYdgK2yOfp4P5E4guvsSGktsNxs,16795
|
|
7
|
+
felis/types.py,sha256=z_ECfSxpqiFSGppjxKwCO4fPP7TLBaIN3Qo1AGF16Go,4418
|
|
8
|
+
felis/validation.py,sha256=Zq0gyCvPCwRlhfQ-w_p6ccDTkjcyhxSA1-Gr5plXiZI,3465
|
|
9
|
+
felis/version.py,sha256=2dU7WXKzRkyzGTBOBEcitF06p_bPRV6WQw9jHANDoDo,55
|
|
10
|
+
felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
felis/db/_variants.py,sha256=zCuXDgU_x_pTZcWkBLgqQCiOhlA6y2tBt-PUQfafwmM,3368
|
|
12
|
+
felis/db/sqltypes.py,sha256=yFlautQ1hv21MHF4AIfBp7_2m1-exKBfc76xYsMHBgk,5735
|
|
13
|
+
lsst_felis-27.2024.1800.dist-info/COPYRIGHT,sha256=bUmNy19uUxqITMpjeHFe69q3IzQpjxvvBw6oV7kR7ho,129
|
|
14
|
+
lsst_felis-27.2024.1800.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
15
|
+
lsst_felis-27.2024.1800.dist-info/METADATA,sha256=6iicjHwqksW8KOYVpkvhkfnzBkaojndCqHdRGTds_1M,1191
|
|
16
|
+
lsst_felis-27.2024.1800.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
17
|
+
lsst_felis-27.2024.1800.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
|
|
18
|
+
lsst_felis-27.2024.1800.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
|
|
19
|
+
lsst_felis-27.2024.1800.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
20
|
+
lsst_felis-27.2024.1800.dist-info/RECORD,,
|