lsst-felis 27.2024.2400__py3-none-any.whl → 27.2024.2600__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/datamodel.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Define Pydantic data models for Felis."""
2
+
1
3
  # This file is part of felis.
2
4
  #
3
5
  # Developed for the LSST Data Management System.
@@ -42,7 +44,6 @@ __all__ = (
42
44
  "Column",
43
45
  "CheckConstraint",
44
46
  "Constraint",
45
- "DescriptionStr",
46
47
  "ForeignKeyConstraint",
47
48
  "Index",
48
49
  "Schema",
@@ -64,39 +65,47 @@ DESCR_MIN_LENGTH = 3
64
65
  """Minimum length for a description field."""
65
66
 
66
67
  DescriptionStr: TypeAlias = Annotated[str, Field(min_length=DESCR_MIN_LENGTH)]
67
- """Define a type for a description string, which must be three or more
68
- characters long. Stripping of whitespace is done globally on all str fields."""
68
+ """Type for a description, which must be three or more characters long."""
69
69
 
70
70
 
71
71
  class BaseObject(BaseModel):
72
- """Base class for all Felis objects."""
72
+ """Base model.
73
+
74
+ All classes representing objects in the Felis data model should inherit
75
+ from this class.
76
+ """
73
77
 
74
78
  model_config = CONFIG
75
79
  """Pydantic model configuration."""
76
80
 
77
81
  name: str
78
- """The name of the database object.
79
-
80
- All Felis database objects must have a name.
81
- """
82
+ """Name of the database object."""
82
83
 
83
84
  id: str = Field(alias="@id")
84
- """The unique identifier of the database object.
85
-
86
- All Felis database objects must have a unique identifier.
87
- """
85
+ """Unique identifier of the database object."""
88
86
 
89
87
  description: DescriptionStr | None = None
90
- """A description of the database object."""
88
+ """Description of the database object."""
91
89
 
92
90
  votable_utype: str | None = Field(None, alias="votable:utype")
93
- """The VOTable utype (usage-specific or unique type) of the object."""
91
+ """VOTable utype (usage-specific or unique type) of the object."""
94
92
 
95
93
  @model_validator(mode="after")
96
94
  def check_description(self, info: ValidationInfo) -> BaseObject:
97
- """Check that the description is present if required."""
95
+ """Check that the description is present if required.
96
+
97
+ Parameters
98
+ ----------
99
+ info
100
+ Validation context used to determine if the check is enabled.
101
+
102
+ Returns
103
+ -------
104
+ `BaseObject`
105
+ The object being validated.
106
+ """
98
107
  context = info.context
99
- if not context or not context.get("require_description", False):
108
+ if not context or not context.get("check_description", False):
100
109
  return self
101
110
  if self.description is None or self.description == "":
102
111
  raise ValueError("Description is required and must be non-empty")
@@ -124,61 +133,66 @@ class DataType(StrEnum):
124
133
 
125
134
 
126
135
  class Column(BaseObject):
127
- """A column in a table."""
136
+ """Column model."""
128
137
 
129
138
  datatype: DataType
130
- """The datatype of the column."""
139
+ """Datatype of the column."""
131
140
 
132
141
  length: int | None = Field(None, gt=0)
133
- """The length of the column."""
142
+ """Length of the column."""
134
143
 
135
144
  nullable: bool = True
136
145
  """Whether the column can be ``NULL``."""
137
146
 
138
147
  value: str | int | float | bool | None = None
139
- """The default value of the column."""
148
+ """Default value of the column."""
140
149
 
141
150
  autoincrement: bool | None = None
142
151
  """Whether the column is autoincremented."""
143
152
 
144
153
  mysql_datatype: str | None = Field(None, alias="mysql:datatype")
145
- """The MySQL datatype of the column."""
154
+ """MySQL datatype override on the column."""
146
155
 
147
156
  postgresql_datatype: str | None = Field(None, alias="postgresql:datatype")
148
- """The PostgreSQL datatype of the column."""
157
+ """PostgreSQL datatype override on the column."""
149
158
 
150
159
  ivoa_ucd: str | None = Field(None, alias="ivoa:ucd")
151
- """The IVOA UCD of the column."""
160
+ """IVOA UCD of the column."""
152
161
 
153
162
  fits_tunit: str | None = Field(None, alias="fits:tunit")
154
- """The FITS TUNIT of the column."""
163
+ """FITS TUNIT of the column."""
155
164
 
156
165
  ivoa_unit: str | None = Field(None, alias="ivoa:unit")
157
- """The IVOA unit of the column."""
166
+ """IVOA unit of the column."""
158
167
 
159
168
  tap_column_index: int | None = Field(None, alias="tap:column_index")
160
- """The TAP_SCHEMA column index of the column."""
169
+ """TAP_SCHEMA column index of the column."""
161
170
 
162
171
  tap_principal: int | None = Field(0, alias="tap:principal", ge=0, le=1)
163
- """Whether this is a TAP_SCHEMA principal column; can be either 0 or 1.
164
- """
172
+ """Whether this is a TAP_SCHEMA principal column."""
165
173
 
166
174
  votable_arraysize: int | Literal["*"] | None = Field(None, alias="votable:arraysize")
167
- """The VOTable arraysize of the column."""
175
+ """VOTable arraysize of the column."""
168
176
 
169
177
  tap_std: int | None = Field(0, alias="tap:std", ge=0, le=1)
170
178
  """TAP_SCHEMA indication that this column is defined by an IVOA standard.
171
179
  """
172
180
 
173
181
  votable_xtype: str | None = Field(None, alias="votable:xtype")
174
- """The VOTable xtype (extended type) of the column."""
182
+ """VOTable xtype (extended type) of the column."""
175
183
 
176
184
  votable_datatype: str | None = Field(None, alias="votable:datatype")
177
- """The VOTable datatype of the column."""
185
+ """VOTable datatype of the column."""
178
186
 
179
187
  @model_validator(mode="after")
180
188
  def check_value(self) -> Column:
181
- """Check that the default value is valid."""
189
+ """Check that the default value is valid.
190
+
191
+ Returns
192
+ -------
193
+ `Column`
194
+ The column being validated.
195
+ """
182
196
  if (value := self.value) is not None:
183
197
  if value is not None and self.autoincrement is True:
184
198
  raise ValueError("Column cannot have both a default value and be autoincremented")
@@ -200,7 +214,18 @@ class Column(BaseObject):
200
214
  @field_validator("ivoa_ucd")
201
215
  @classmethod
202
216
  def check_ivoa_ucd(cls, ivoa_ucd: str) -> str:
203
- """Check that IVOA UCD values are valid."""
217
+ """Check that IVOA UCD values are valid.
218
+
219
+ Parameters
220
+ ----------
221
+ ivoa_ucd
222
+ IVOA UCD value to check.
223
+
224
+ Returns
225
+ -------
226
+ `str`
227
+ The IVOA UCD value if it is valid.
228
+ """
204
229
  if ivoa_ucd is not None:
205
230
  try:
206
231
  ucd.parse_ucd(ivoa_ucd, check_controlled_vocabulary=True, has_colon=";" in ivoa_ucd)
@@ -208,12 +233,24 @@ class Column(BaseObject):
208
233
  raise ValueError(f"Invalid IVOA UCD: {e}")
209
234
  return ivoa_ucd
210
235
 
211
- @model_validator(mode="before")
212
- @classmethod
213
- def check_units(cls, values: dict[str, Any]) -> dict[str, Any]:
214
- """Check that units are valid."""
215
- fits_unit = values.get("fits:tunit")
216
- ivoa_unit = values.get("ivoa:unit")
236
+ @model_validator(mode="after")
237
+ def check_units(self) -> Column:
238
+ """Check that the ``fits:tunit`` or ``ivoa:unit`` field has valid
239
+ units according to astropy. Only one may be provided.
240
+
241
+ Returns
242
+ -------
243
+ `Column`
244
+ The column being validated.
245
+
246
+ Raises
247
+ ------
248
+ ValueError
249
+ If both FITS and IVOA units are provided, or if the unit is
250
+ invalid.
251
+ """
252
+ fits_unit = self.fits_tunit
253
+ ivoa_unit = self.ivoa_unit
217
254
 
218
255
  if fits_unit and ivoa_unit:
219
256
  raise ValueError("Column cannot have both FITS and IVOA units")
@@ -225,12 +262,28 @@ class Column(BaseObject):
225
262
  except ValueError as e:
226
263
  raise ValueError(f"Invalid unit: {e}")
227
264
 
228
- return values
265
+ return self
229
266
 
230
267
  @model_validator(mode="before")
231
268
  @classmethod
232
269
  def check_length(cls, values: dict[str, Any]) -> dict[str, Any]:
233
- """Check that a valid length is provided for sized types."""
270
+ """Check that a valid length is provided for sized types.
271
+
272
+ Parameters
273
+ ----------
274
+ values
275
+ Values of the column.
276
+
277
+ Returns
278
+ -------
279
+ `dict` [ `str`, `Any` ]
280
+ The values of the column.
281
+
282
+ Raises
283
+ ------
284
+ ValueError
285
+ If a length is not provided for a sized type.
286
+ """
234
287
  datatype = values.get("datatype")
235
288
  if datatype is None:
236
289
  # Skip this validation if datatype is not provided
@@ -250,8 +303,24 @@ class Column(BaseObject):
250
303
  return values
251
304
 
252
305
  @model_validator(mode="after")
253
- def check_datatypes(self, info: ValidationInfo) -> Column:
254
- """Check for redundant datatypes on columns."""
306
+ def check_redundant_datatypes(self, info: ValidationInfo) -> Column:
307
+ """Check for redundant datatypes on columns.
308
+
309
+ Parameters
310
+ ----------
311
+ info
312
+ Validation context used to determine if the check is enabled.
313
+
314
+ Returns
315
+ -------
316
+ `Column`
317
+ The column being validated.
318
+
319
+ Raises
320
+ ------
321
+ ValueError
322
+ If a datatype override is redundant.
323
+ """
255
324
  context = info.context
256
325
  if not context or not context.get("check_redundant_datatypes", False):
257
326
  return self
@@ -296,51 +365,69 @@ class Column(BaseObject):
296
365
 
297
366
 
298
367
  class Constraint(BaseObject):
299
- """A database table constraint."""
368
+ """Table constraint model."""
300
369
 
301
370
  deferrable: bool = False
302
- """If `True` then this constraint will be declared as deferrable."""
371
+ """Whether this constraint will be declared as deferrable."""
303
372
 
304
373
  initially: str | None = None
305
- """Value for ``INITIALLY`` clause, only used if ``deferrable`` is True."""
374
+ """Value for ``INITIALLY`` clause; only used if `deferrable` is
375
+ `True`."""
306
376
 
307
377
  annotations: Mapping[str, Any] = Field(default_factory=dict)
308
378
  """Additional annotations for this constraint."""
309
379
 
310
380
  type: str | None = Field(None, alias="@type")
311
- """The type of the constraint."""
381
+ """Type of the constraint."""
312
382
 
313
383
 
314
384
  class CheckConstraint(Constraint):
315
- """A check constraint on a table."""
385
+ """Table check constraint model."""
316
386
 
317
387
  expression: str
318
- """The expression for the check constraint."""
388
+ """Expression for the check constraint."""
319
389
 
320
390
 
321
391
  class UniqueConstraint(Constraint):
322
- """A unique constraint on a table."""
392
+ """Table unique constraint model."""
323
393
 
324
394
  columns: list[str]
325
- """The columns in the unique constraint."""
395
+ """Columns in the unique constraint."""
326
396
 
327
397
 
328
398
  class Index(BaseObject):
329
- """A database table index.
399
+ """Table index model.
330
400
 
331
401
  An index can be defined on either columns or expressions, but not both.
332
402
  """
333
403
 
334
404
  columns: list[str] | None = None
335
- """The columns in the index."""
405
+ """Columns in the index."""
336
406
 
337
407
  expressions: list[str] | None = None
338
- """The expressions in the index."""
408
+ """Expressions in the index."""
339
409
 
340
410
  @model_validator(mode="before")
341
411
  @classmethod
342
412
  def check_columns_or_expressions(cls, values: dict[str, Any]) -> dict[str, Any]:
343
- """Check that columns or expressions are specified, but not both."""
413
+ """Check that columns or expressions are specified, but not both.
414
+
415
+ Parameters
416
+ ----------
417
+ values
418
+ Values of the index.
419
+
420
+ Returns
421
+ -------
422
+ `dict` [ `str`, `Any` ]
423
+ The values of the index.
424
+
425
+ Raises
426
+ ------
427
+ ValueError
428
+ If both columns and expressions are specified, or if neither are
429
+ specified.
430
+ """
344
431
  if "columns" in values and "expressions" in values:
345
432
  raise ValueError("Defining columns and expressions is not valid")
346
433
  elif "columns" not in values and "expressions" not in values:
@@ -349,9 +436,15 @@ class Index(BaseObject):
349
436
 
350
437
 
351
438
  class ForeignKeyConstraint(Constraint):
352
- """A foreign key constraint on a table.
439
+ """Table foreign key constraint model.
440
+
441
+ This constraint is used to define a foreign key relationship between two
442
+ tables in the schema.
353
443
 
354
- These will be reflected in the TAP_SCHEMA keys and key_columns data.
444
+ Notes
445
+ -----
446
+ These relationships will be reflected in the TAP_SCHEMA ``keys`` and
447
+ ``key_columns`` data.
355
448
  """
356
449
 
357
450
  columns: list[str]
@@ -362,41 +455,46 @@ class ForeignKeyConstraint(Constraint):
362
455
 
363
456
 
364
457
  class Table(BaseObject):
365
- """A database table."""
458
+ """Table model."""
366
459
 
367
460
  columns: Sequence[Column]
368
- """The columns in the table."""
461
+ """Columns in the table."""
369
462
 
370
463
  constraints: list[Constraint] = Field(default_factory=list)
371
- """The constraints on the table."""
464
+ """Constraints on the table."""
372
465
 
373
466
  indexes: list[Index] = Field(default_factory=list)
374
- """The indexes on the table."""
467
+ """Indexes on the table."""
375
468
 
376
469
  primary_key: str | list[str] | None = Field(None, alias="primaryKey")
377
- """The primary key of the table."""
470
+ """Primary key of the table."""
378
471
 
379
472
  tap_table_index: int | None = Field(None, alias="tap:table_index")
380
- """The IVOA TAP_SCHEMA table index of the table."""
473
+ """IVOA TAP_SCHEMA table index of the table."""
381
474
 
382
475
  mysql_engine: str | None = Field(None, alias="mysql:engine")
383
- """The mysql engine to use for the table.
384
-
385
- For now this is a freeform string but it could be constrained to a list of
386
- known engines in the future.
387
- """
476
+ """MySQL engine to use for the table."""
388
477
 
389
478
  mysql_charset: str | None = Field(None, alias="mysql:charset")
390
- """The mysql charset to use for the table.
391
-
392
- For now this is a freeform string but it could be constrained to a list of
393
- known charsets in the future.
394
- """
479
+ """MySQL charset to use for the table."""
395
480
 
396
481
  @model_validator(mode="before")
397
482
  @classmethod
398
483
  def create_constraints(cls, values: dict[str, Any]) -> dict[str, Any]:
399
- """Create constraints from the ``constraints`` field."""
484
+ """Create specific constraint types from the data in the
485
+ ``constraints`` field of a table.
486
+
487
+ Parameters
488
+ ----------
489
+ values
490
+ The values of the table containing the constraint data.
491
+
492
+ Returns
493
+ -------
494
+ `dict` [ `str`, `Any` ]
495
+ The values of the table with the constraints converted to their
496
+ respective types.
497
+ """
400
498
  if "constraints" in values:
401
499
  new_constraints: list[Constraint] = []
402
500
  for item in values["constraints"]:
@@ -414,14 +512,84 @@ class Table(BaseObject):
414
512
  @field_validator("columns", mode="after")
415
513
  @classmethod
416
514
  def check_unique_column_names(cls, columns: list[Column]) -> list[Column]:
417
- """Check that column names are unique."""
515
+ """Check that column names are unique.
516
+
517
+ Parameters
518
+ ----------
519
+ columns
520
+ The columns to check.
521
+
522
+ Returns
523
+ -------
524
+ `list` [ `Column` ]
525
+ The columns if they are unique.
526
+
527
+ Raises
528
+ ------
529
+ ValueError
530
+ If column names are not unique.
531
+ """
418
532
  if len(columns) != len(set(column.name for column in columns)):
419
533
  raise ValueError("Column names must be unique")
420
534
  return columns
421
535
 
536
+ @model_validator(mode="after")
537
+ def check_tap_table_index(self, info: ValidationInfo) -> Table:
538
+ """Check that the table has a TAP table index.
539
+
540
+ Parameters
541
+ ----------
542
+ info
543
+ Validation context used to determine if the check is enabled.
544
+
545
+ Returns
546
+ -------
547
+ `Table`
548
+ The table being validated.
549
+
550
+ Raises
551
+ ------
552
+ ValueError
553
+ If the table is missing a TAP table index.
554
+ """
555
+ context = info.context
556
+ if not context or not context.get("check_tap_table_indexes", False):
557
+ return self
558
+ if self.tap_table_index is None:
559
+ raise ValueError("Table is missing a TAP table index")
560
+ return self
561
+
562
+ @model_validator(mode="after")
563
+ def check_tap_principal(self, info: ValidationInfo) -> Table:
564
+ """Check that at least one column is flagged as 'principal' for TAP
565
+ purposes.
566
+
567
+ Parameters
568
+ ----------
569
+ info
570
+ Validation context used to determine if the check is enabled.
571
+
572
+ Returns
573
+ -------
574
+ `Table`
575
+ The table being validated.
576
+
577
+ Raises
578
+ ------
579
+ ValueError
580
+ If the table is missing a column flagged as 'principal'.
581
+ """
582
+ context = info.context
583
+ if not context or not context.get("check_tap_principal", False):
584
+ return self
585
+ for col in self.columns:
586
+ if col.tap_principal == 1:
587
+ return self
588
+ raise ValueError(f"Table '{self.name}' is missing at least one column designated as 'tap:principal'")
589
+
422
590
 
423
591
  class SchemaVersion(BaseModel):
424
- """The version of the schema."""
592
+ """Schema version model."""
425
593
 
426
594
  current: str
427
595
  """The current version of the schema."""
@@ -434,15 +602,16 @@ class SchemaVersion(BaseModel):
434
602
 
435
603
 
436
604
  class SchemaIdVisitor:
437
- """Visitor to build a Schema object's map of IDs to objects.
605
+ """Visit a schema and build the map of IDs to objects.
438
606
 
607
+ Notes
608
+ -----
439
609
  Duplicates are added to a set when they are encountered, which can be
440
- accessed via the `duplicates` attribute. The presence of duplicates will
610
+ accessed via the ``duplicates`` attribute. The presence of duplicates will
441
611
  not throw an error. Only the first object with a given ID will be added to
442
- the map, but this should not matter, since a ValidationError will be thrown
443
- by the `model_validator` method if any duplicates are found in the schema.
444
-
445
- This class is intended for internal use only.
612
+ the map, but this should not matter, since a ``ValidationError`` will be
613
+ thrown by the ``model_validator`` method if any duplicates are found in the
614
+ schema.
446
615
  """
447
616
 
448
617
  def __init__(self) -> None:
@@ -451,7 +620,13 @@ class SchemaIdVisitor:
451
620
  self.duplicates: set[str] = set()
452
621
 
453
622
  def add(self, obj: BaseObject) -> None:
454
- """Add an object to the ID map."""
623
+ """Add an object to the ID map.
624
+
625
+ Parameters
626
+ ----------
627
+ obj
628
+ The object to add to the ID map.
629
+ """
455
630
  if hasattr(obj, "id"):
456
631
  obj_id = getattr(obj, "id")
457
632
  if self.schema is not None:
@@ -461,8 +636,15 @@ class SchemaIdVisitor:
461
636
  self.schema.id_map[obj_id] = obj
462
637
 
463
638
  def visit_schema(self, schema: Schema) -> None:
464
- """Visit the schema object that was added during initialization.
639
+ """Visit the objects in a schema and build the ID map.
640
+
641
+ Parameters
642
+ ----------
643
+ schema
644
+ The schema object to visit.
465
645
 
646
+ Notes
647
+ -----
466
648
  This will set an internal variable pointing to the schema object.
467
649
  """
468
650
  self.schema = schema
@@ -472,7 +654,13 @@ class SchemaIdVisitor:
472
654
  self.visit_table(table)
473
655
 
474
656
  def visit_table(self, table: Table) -> None:
475
- """Visit a table object."""
657
+ """Visit a table object.
658
+
659
+ Parameters
660
+ ----------
661
+ table
662
+ The table object to visit.
663
+ """
476
664
  self.add(table)
477
665
  for column in table.columns:
478
666
  self.visit_column(column)
@@ -480,16 +668,31 @@ class SchemaIdVisitor:
480
668
  self.visit_constraint(constraint)
481
669
 
482
670
  def visit_column(self, column: Column) -> None:
483
- """Visit a column object."""
671
+ """Visit a column object.
672
+
673
+ Parameters
674
+ ----------
675
+ column
676
+ The column object to visit.
677
+ """
484
678
  self.add(column)
485
679
 
486
680
  def visit_constraint(self, constraint: Constraint) -> None:
487
- """Visit a constraint object."""
681
+ """Visit a constraint object.
682
+
683
+ Parameters
684
+ ----------
685
+ constraint
686
+ The constraint object to visit.
687
+ """
488
688
  self.add(constraint)
489
689
 
490
690
 
491
691
  class Schema(BaseObject):
492
- """The database schema containing the tables."""
692
+ """Database schema model.
693
+
694
+ This is the root object of the Felis data model.
695
+ """
493
696
 
494
697
  version: SchemaVersion | str | None = None
495
698
  """The version of the schema."""
@@ -503,17 +706,65 @@ class Schema(BaseObject):
503
706
  @field_validator("tables", mode="after")
504
707
  @classmethod
505
708
  def check_unique_table_names(cls, tables: list[Table]) -> list[Table]:
506
- """Check that table names are unique."""
709
+ """Check that table names are unique.
710
+
711
+ Parameters
712
+ ----------
713
+ tables
714
+ The tables to check.
715
+
716
+ Returns
717
+ -------
718
+ `list` [ `Table` ]
719
+ The tables if they are unique.
720
+
721
+ Raises
722
+ ------
723
+ ValueError
724
+ If table names are not unique.
725
+ """
507
726
  if len(tables) != len(set(table.name for table in tables)):
508
727
  raise ValueError("Table names must be unique")
509
728
  return tables
510
729
 
730
+ @model_validator(mode="after")
731
+ def check_tap_table_indexes(self, info: ValidationInfo) -> Schema:
732
+ """Check that the TAP table indexes are unique.
733
+
734
+ Parameters
735
+ ----------
736
+ info
737
+ The validation context used to determine if the check is enabled.
738
+
739
+ Returns
740
+ -------
741
+ `Schema`
742
+ The schema being validated.
743
+ """
744
+ context = info.context
745
+ if not context or not context.get("check_tap_table_indexes", False):
746
+ return self
747
+ table_indicies = set()
748
+ for table in self.tables:
749
+ table_index = table.tap_table_index
750
+ if table_index is not None:
751
+ if table_index in table_indicies:
752
+ raise ValueError(f"Duplicate 'tap:table_index' value {table_index} found in schema")
753
+ table_indicies.add(table_index)
754
+ return self
755
+
511
756
  def _create_id_map(self: Schema) -> Schema:
512
757
  """Create a map of IDs to objects.
513
758
 
514
- This method should not be called by users. It is called automatically
515
- by the ``model_post_init()`` method. If the ID map is already
516
- populated, this method will return immediately.
759
+ Raises
760
+ ------
761
+ ValueError
762
+ If duplicate IDs are found in the schema.
763
+
764
+ Notes
765
+ -----
766
+ This is called automatically by the `model_post_init` method. If the
767
+ ID map is already populated, this method will return immediately.
517
768
  """
518
769
  if len(self.id_map):
519
770
  logger.debug("Ignoring call to create_id_map() - ID map was already populated")
@@ -527,15 +778,46 @@ class Schema(BaseObject):
527
778
  return self
528
779
 
529
780
  def model_post_init(self, ctx: Any) -> None:
530
- """Post-initialization hook for the model."""
781
+ """Post-initialization hook for the model.
782
+
783
+ Parameters
784
+ ----------
785
+ ctx
786
+ The context object which was passed to the model.
787
+
788
+ Notes
789
+ -----
790
+ This method is called automatically by Pydantic after the model is
791
+ initialized. It is used to create the ID map for the schema.
792
+
793
+ The ``ctx`` argument has the type `Any` because this is the function
794
+ signature in Pydantic itself.
795
+ """
531
796
  self._create_id_map()
532
797
 
533
798
  def __getitem__(self, id: str) -> BaseObject:
534
- """Get an object by its ID."""
799
+ """Get an object by its ID.
800
+
801
+ Parameters
802
+ ----------
803
+ id
804
+ The ID of the object to get.
805
+
806
+ Raises
807
+ ------
808
+ KeyError
809
+ If the object with the given ID is not found in the schema.
810
+ """
535
811
  if id not in self:
536
812
  raise KeyError(f"Object with ID '{id}' not found in schema")
537
813
  return self.id_map[id]
538
814
 
539
815
  def __contains__(self, id: str) -> bool:
540
- """Check if an object with the given ID is in the schema."""
816
+ """Check if an object with the given ID is in the schema.
817
+
818
+ Parameters
819
+ ----------
820
+ id
821
+ The ID of the object to check.
822
+ """
541
823
  return id in self.id_map