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/simple.py DELETED
@@ -1,424 +0,0 @@
1
- # This file is part of felis.
2
- #
3
- # Developed for the LSST Data Management System.
4
- # This product includes software developed by the LSST Project
5
- # (https://www.lsst.org).
6
- # See the COPYRIGHT file at the top-level directory of this distribution
7
- # for details of code ownership.
8
- #
9
- # This program is free software: you can redistribute it and/or modify
10
- # it under the terms of the GNU General Public License as published by
11
- # the Free Software Foundation, either version 3 of the License, or
12
- # (at your option) any later version.
13
- #
14
- # This program is distributed in the hope that it will be useful,
15
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
- # GNU General Public License for more details.
18
- #
19
- # You should have received a copy of the GNU General Public License
20
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
21
-
22
- from __future__ import annotations
23
-
24
- __all__ = [
25
- "CheckConstraint",
26
- "Column",
27
- "Constraint",
28
- "ForeignKeyConstraint",
29
- "Index",
30
- "Schema",
31
- "SchemaVersion",
32
- "SimpleVisitor",
33
- "Table",
34
- "UniqueConstraint",
35
- ]
36
-
37
- import dataclasses
38
- import logging
39
- from collections.abc import Iterable, Mapping, MutableMapping
40
- from typing import Any, cast
41
-
42
- from .check import FelisValidator
43
- from .types import FelisType
44
- from .visitor import Visitor
45
-
46
- _Mapping = Mapping[str, Any]
47
-
48
- logger = logging.getLogger("felis.generic")
49
-
50
-
51
- def _strip_keys(map: _Mapping, keys: Iterable[str]) -> _Mapping:
52
- """Return a copy of a dictionary with some keys removed."""
53
- keys = set(keys)
54
- return {key: value for key, value in map.items() if key not in keys}
55
-
56
-
57
- def _make_iterable(obj: str | Iterable[str]) -> Iterable[str]:
58
- """Make an iterable out of string or list of strings."""
59
- if isinstance(obj, str):
60
- yield obj
61
- else:
62
- yield from obj
63
-
64
-
65
- @dataclasses.dataclass
66
- class Column:
67
- """Column representation in schema."""
68
-
69
- name: str
70
- """Column name."""
71
-
72
- id: str
73
- """Felis ID for this column."""
74
-
75
- datatype: type[FelisType]
76
- """Column type, one of the types/classes defined in `types`."""
77
-
78
- length: int | None = None
79
- """Optional length for string/binary columns"""
80
-
81
- nullable: bool = True
82
- """True for nullable columns."""
83
-
84
- value: Any = None
85
- """Default value for column, can be `None`."""
86
-
87
- autoincrement: bool | None = None
88
- """Unspecified value results in `None`."""
89
-
90
- description: str | None = None
91
- """Column description."""
92
-
93
- annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
94
- """Additional annotations for this column."""
95
-
96
- table: Table | None = None
97
- """Table which defines this column, usually not `None`."""
98
-
99
-
100
- @dataclasses.dataclass
101
- class Index:
102
- """Index representation."""
103
-
104
- name: str
105
- """index name, can be empty."""
106
-
107
- id: str
108
- """Felis ID for this index."""
109
-
110
- columns: list[Column] = dataclasses.field(default_factory=list)
111
- """List of columns in index, one of the ``columns`` or ``expressions``
112
- must be non-empty.
113
- """
114
-
115
- expressions: list[str] = dataclasses.field(default_factory=list)
116
- """List of expressions in index, one of the ``columns`` or ``expressions``
117
- must be non-empty.
118
- """
119
-
120
- description: str | None = None
121
- """Index description."""
122
-
123
- annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
124
- """Additional annotations for this index."""
125
-
126
-
127
- @dataclasses.dataclass
128
- class Constraint:
129
- """Constraint description, this is a base class, actual constraints will be
130
- instances of one of the subclasses.
131
- """
132
-
133
- name: str | None
134
- """Constraint name."""
135
-
136
- id: str
137
- """Felis ID for this constraint."""
138
-
139
- deferrable: bool = False
140
- """If `True` then this constraint will be declared as deferrable."""
141
-
142
- initially: str | None = None
143
- """Value for ``INITIALLY`` clause, only used of ``deferrable`` is True."""
144
-
145
- description: str | None = None
146
- """Constraint description."""
147
-
148
- annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
149
- """Additional annotations for this constraint."""
150
-
151
-
152
- @dataclasses.dataclass
153
- class UniqueConstraint(Constraint):
154
- """Description of unique constraint."""
155
-
156
- columns: list[Column] = dataclasses.field(default_factory=list)
157
- """List of columns in this constraint, all columns belong to the same table
158
- as the constraint itself.
159
- """
160
-
161
-
162
- @dataclasses.dataclass
163
- class ForeignKeyConstraint(Constraint):
164
- """Description of foreign key constraint."""
165
-
166
- columns: list[Column] = dataclasses.field(default_factory=list)
167
- """List of columns in this constraint, all columns belong to the same table
168
- as the constraint itself.
169
- """
170
-
171
- referenced_columns: list[Column] = dataclasses.field(default_factory=list)
172
- """List of referenced columns, the number of columns must be the same as in
173
- ``Constraint.columns`` list. All columns must belong to the same table,
174
- which is different from the table of this constraint.
175
- """
176
-
177
-
178
- @dataclasses.dataclass
179
- class CheckConstraint(Constraint):
180
- """Description of check constraint."""
181
-
182
- expression: str = ""
183
- """Expression on one or more columns on the table, must be non-empty."""
184
-
185
-
186
- @dataclasses.dataclass
187
- class Table:
188
- """Description of a single table schema."""
189
-
190
- name: str
191
- """Table name."""
192
-
193
- id: str
194
- """Felis ID for this table."""
195
-
196
- columns: list[Column]
197
- """List of Column instances."""
198
-
199
- primary_key: list[Column]
200
- """List of Column that constitute a primary key, may be empty."""
201
-
202
- constraints: list[Constraint]
203
- """List of Constraint instances, can be empty."""
204
-
205
- indexes: list[Index]
206
- """List of Index instances, can be empty."""
207
-
208
- description: str | None = None
209
- """Table description."""
210
-
211
- annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
212
- """Additional annotations for this table."""
213
-
214
-
215
- @dataclasses.dataclass
216
- class SchemaVersion:
217
- """Schema versioning description."""
218
-
219
- current: str
220
- """Current schema version defined by the document."""
221
-
222
- compatible: list[str] | None = None
223
- """Optional list of versions which are compatible with current version."""
224
-
225
- read_compatible: list[str] | None = None
226
- """Optional list of versions with which current version is read-compatible.
227
- """
228
-
229
-
230
- @dataclasses.dataclass
231
- class Schema:
232
- """Complete schema description, collection of tables."""
233
-
234
- name: str
235
- """Schema name."""
236
-
237
- id: str
238
- """Felis ID for this schema."""
239
-
240
- tables: list[Table]
241
- """Collection of table definitions."""
242
-
243
- version: SchemaVersion | None = None
244
- """Schema version description."""
245
-
246
- description: str | None = None
247
- """Schema description."""
248
-
249
- annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
250
- """Additional annotations for this table."""
251
-
252
-
253
- class SimpleVisitor(Visitor[Schema, Table, Column, list[Column], Constraint, Index, SchemaVersion]):
254
- """Visitor implementation class that produces a simple in-memory
255
- representation of Felis schema using classes `Schema`, `Table`, etc. from
256
- this module.
257
-
258
- Notes
259
- -----
260
- Implementation of this visitor class uses `FelisValidator` to validate the
261
- contents of the schema. All visit methods can raise the same exceptions as
262
- corresponding `FelisValidator` methods (usually `ValueError`).
263
- """
264
-
265
- def __init__(self) -> None:
266
- self.checker = FelisValidator()
267
- self.column_ids: MutableMapping[str, Column] = {}
268
-
269
- def visit_schema(self, schema_obj: _Mapping) -> Schema:
270
- # Docstring is inherited.
271
- self.checker.check_schema(schema_obj)
272
-
273
- version_obj = schema_obj.get("version")
274
-
275
- schema = Schema(
276
- name=schema_obj["name"],
277
- id=schema_obj["@id"],
278
- tables=[self.visit_table(t, schema_obj) for t in schema_obj["tables"]],
279
- version=self.visit_schema_version(version_obj, schema_obj) if version_obj is not None else None,
280
- description=schema_obj.get("description"),
281
- annotations=_strip_keys(schema_obj, ["name", "@id", "tables", "description"]),
282
- )
283
- return schema
284
-
285
- def visit_schema_version(
286
- self, version_obj: str | Mapping[str, Any], schema_obj: Mapping[str, Any]
287
- ) -> SchemaVersion:
288
- # Docstring is inherited.
289
- self.checker.check_schema_version(version_obj, schema_obj)
290
-
291
- if isinstance(version_obj, str):
292
- return SchemaVersion(current=version_obj)
293
- else:
294
- return SchemaVersion(
295
- current=cast(str, version_obj["current"]),
296
- compatible=version_obj.get("compatible"),
297
- read_compatible=version_obj.get("read_compatible"),
298
- )
299
-
300
- def visit_table(self, table_obj: _Mapping, schema_obj: _Mapping) -> Table:
301
- # Docstring is inherited.
302
- self.checker.check_table(table_obj, schema_obj)
303
-
304
- columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
305
- table = Table(
306
- name=table_obj["name"],
307
- id=table_obj["@id"],
308
- columns=columns,
309
- primary_key=self.visit_primary_key(table_obj.get("primaryKey", []), table_obj),
310
- constraints=[self.visit_constraint(c, table_obj) for c in table_obj.get("constraints", [])],
311
- indexes=[self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])],
312
- description=table_obj.get("description"),
313
- annotations=_strip_keys(
314
- table_obj, ["name", "@id", "columns", "primaryKey", "constraints", "indexes", "description"]
315
- ),
316
- )
317
- for column in columns:
318
- column.table = table
319
- return table
320
-
321
- def visit_column(self, column_obj: _Mapping, table_obj: _Mapping) -> Column:
322
- # Docstring is inherited.
323
- self.checker.check_column(column_obj, table_obj)
324
-
325
- datatype = FelisType.felis_type(column_obj["datatype"])
326
-
327
- column = Column(
328
- name=column_obj["name"],
329
- id=column_obj["@id"],
330
- datatype=datatype,
331
- length=column_obj.get("length"),
332
- value=column_obj.get("value"),
333
- description=column_obj.get("description"),
334
- nullable=column_obj.get("nullable", True),
335
- autoincrement=column_obj.get("autoincrement"),
336
- annotations=_strip_keys(
337
- column_obj,
338
- ["name", "@id", "datatype", "length", "nullable", "value", "autoincrement", "description"],
339
- ),
340
- )
341
- if column.id in self.column_ids:
342
- logger.warning(f"Duplication of @id {column.id}")
343
- self.column_ids[column.id] = column
344
- return column
345
-
346
- def visit_primary_key(self, primary_key_obj: str | Iterable[str], table_obj: _Mapping) -> list[Column]:
347
- # Docstring is inherited.
348
- self.checker.check_primary_key(primary_key_obj, table_obj)
349
- if primary_key_obj:
350
- columns = [self.column_ids[c_id] for c_id in _make_iterable(primary_key_obj)]
351
- return columns
352
- return []
353
-
354
- def visit_constraint(self, constraint_obj: _Mapping, table_obj: _Mapping) -> Constraint:
355
- # Docstring is inherited.
356
- self.checker.check_constraint(constraint_obj, table_obj)
357
-
358
- constraint_type = constraint_obj["@type"]
359
- if constraint_type == "Unique":
360
- return UniqueConstraint(
361
- name=constraint_obj.get("name"),
362
- id=constraint_obj["@id"],
363
- columns=[self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["columns"])],
364
- deferrable=constraint_obj.get("deferrable", False),
365
- initially=constraint_obj.get("initially"),
366
- description=constraint_obj.get("description"),
367
- annotations=_strip_keys(
368
- constraint_obj,
369
- ["name", "@type", "@id", "columns", "deferrable", "initially", "description"],
370
- ),
371
- )
372
- elif constraint_type == "ForeignKey":
373
- return ForeignKeyConstraint(
374
- name=constraint_obj.get("name"),
375
- id=constraint_obj["@id"],
376
- columns=[self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["columns"])],
377
- referenced_columns=[
378
- self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["referencedColumns"])
379
- ],
380
- deferrable=constraint_obj.get("deferrable", False),
381
- initially=constraint_obj.get("initially"),
382
- description=constraint_obj.get("description"),
383
- annotations=_strip_keys(
384
- constraint_obj,
385
- [
386
- "name",
387
- "@id",
388
- "@type",
389
- "columns",
390
- "deferrable",
391
- "initially",
392
- "referencedColumns",
393
- "description",
394
- ],
395
- ),
396
- )
397
- elif constraint_type == "Check":
398
- return CheckConstraint(
399
- name=constraint_obj.get("name"),
400
- id=constraint_obj["@id"],
401
- expression=constraint_obj["expression"],
402
- deferrable=constraint_obj.get("deferrable", False),
403
- initially=constraint_obj.get("initially"),
404
- description=constraint_obj.get("description"),
405
- annotations=_strip_keys(
406
- constraint_obj,
407
- ["name", "@id", "@type", "expression", "deferrable", "initially", "description"],
408
- ),
409
- )
410
- else:
411
- raise ValueError(f"Unexpected constrint type: {constraint_type}")
412
-
413
- def visit_index(self, index_obj: _Mapping, table_obj: _Mapping) -> Index:
414
- # Docstring is inherited.
415
- self.checker.check_index(index_obj, table_obj)
416
-
417
- return Index(
418
- name=index_obj["name"],
419
- id=index_obj["@id"],
420
- columns=[self.column_ids[c_id] for c_id in _make_iterable(index_obj.get("columns", []))],
421
- expressions=index_obj.get("expressions", []),
422
- description=index_obj.get("description"),
423
- annotations=_strip_keys(index_obj, ["name", "@id", "columns", "expressions", "description"]),
424
- )
felis/utils.py DELETED
@@ -1,100 +0,0 @@
1
- # This file is part of felis.
2
- #
3
- # Developed for the LSST Data Management System.
4
- # This product includes software developed by the LSST Project
5
- # (https://www.lsst.org).
6
- # See the COPYRIGHT file at the top-level directory of this distribution
7
- # for details of code ownership.
8
- #
9
- # This program is free software: you can redistribute it and/or modify
10
- # it under the terms of the GNU General Public License as published by
11
- # the Free Software Foundation, either version 3 of the License, or
12
- # (at your option) any later version.
13
- #
14
- # This program is distributed in the hope that it will be useful,
15
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
- # GNU General Public License for more details.
18
- #
19
- # You should have received a copy of the GNU General Public License
20
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
21
-
22
- from __future__ import annotations
23
-
24
- from collections.abc import Iterable, Mapping, MutableMapping
25
- from typing import Any
26
-
27
- _Mapping = Mapping[str, Any]
28
- _MutableMapping = MutableMapping[str, Any]
29
-
30
-
31
- class ReorderingVisitor:
32
- """A visitor that reorders and optionally adds the "@type".
33
-
34
- Parameters
35
- ----------
36
- add_type : `bool`
37
- If true, add the "@type" if it doesn't exist.
38
- """
39
-
40
- def __init__(self, add_type: bool = False):
41
- self.add_type = add_type
42
-
43
- def visit_schema(self, schema_obj: _MutableMapping) -> _Mapping:
44
- """Process schema, the input MUST be a normalized representation."""
45
- # Override with default
46
- tables = [self.visit_table(table_obj, schema_obj) for table_obj in schema_obj["tables"]]
47
- schema_obj["tables"] = tables
48
- if self.add_type:
49
- schema_obj["@type"] = schema_obj.get("@type", "Schema")
50
- return _new_order(
51
- schema_obj, ["@context", "name", "@id", "@type", "description", "tables", "version"]
52
- )
53
-
54
- def visit_table(self, table_obj: _MutableMapping, schema_obj: _Mapping) -> _Mapping:
55
- columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
56
- primary_key = self.visit_primary_key(table_obj.get("primaryKey", []), table_obj)
57
- constraints = [self.visit_constraint(c, table_obj) for c in table_obj.get("constraints", [])]
58
- indexes = [self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])]
59
- table_obj["columns"] = columns
60
- if primary_key:
61
- table_obj["primaryKey"] = primary_key
62
- if constraints:
63
- table_obj["constraints"] = constraints
64
- if indexes:
65
- table_obj["indexes"] = indexes
66
- if self.add_type:
67
- table_obj["@type"] = table_obj.get("@type", "Table")
68
- return _new_order(
69
- table_obj,
70
- ["name", "@id", "@type", "description", "columns", "primaryKey", "constraints", "indexes"],
71
- )
72
-
73
- def visit_column(self, column_obj: _MutableMapping, table_obj: _Mapping) -> _Mapping:
74
- if self.add_type:
75
- column_obj["@type"] = column_obj.get("@type", "Column")
76
- return _new_order(column_obj, ["name", "@id", "@type", "description", "datatype"])
77
-
78
- def visit_primary_key(self, primary_key_obj: _MutableMapping, table: _Mapping) -> _Mapping:
79
- # FIXME: Handle Primary Keys
80
- return primary_key_obj
81
-
82
- def visit_constraint(self, constraint_obj: _MutableMapping, table: _Mapping) -> _Mapping:
83
- # Type MUST be present... we can skip
84
- return _new_order(constraint_obj, ["name", "@id", "@type", "description"])
85
-
86
- def visit_index(self, index_obj: _MutableMapping, table: _Mapping) -> _Mapping:
87
- if self.add_type:
88
- index_obj["@type"] = index_obj.get("@type", "Index")
89
- return _new_order(index_obj, ["name", "@id", "@type", "description"])
90
-
91
-
92
- def _new_order(obj: _Mapping, order: Iterable[str]) -> _Mapping:
93
- reordered_object: _MutableMapping = {}
94
- for name in order:
95
- if name in obj:
96
- reordered_object[name] = obj[name]
97
- for key, value in obj.items():
98
- if key not in reordered_object:
99
- reordered_object[key] = value
100
- return reordered_object