## lamindb.Schema

| class lamindb.Schema(features: list[SQLRecord] | SQLRecordList | list[tuple[Feature, dict]] | None = None, *, slots: dict[str, Schema] | None = None, name: str | None = None, description: str | None = None, itype: str | Registry | FieldAttr | None = None, type: Schema | None | Unset = UNSET, is_type: bool = False, index: Feature | None = None, flexible: bool | None = None, otype: str | None = None, suffix: str | None = None, dtype: str | Type[int | float | str] | None = None, minimal_set: bool = True, maximal_set: bool = False, ordered_set: bool = False, coerce: bool | None = None, n_members: int | None = None, lookup: bool = True, branch: Branch | None = None, space: Space | None = None) |
class lamindb.Schema(*db_args)

 Bases: "SQLRecord", "HasType", "CanCurate", "TracksRun",
 "TracksUpdates"

 Data models that impose structure on artifacts and records.

 The simplest example for a schema is the set of columns of a
 "DataFrame". LaminDB's schemas generalize those of pyarrow,
 pydantic, and pandera in two important ways:

 1. They can be used to validate arbitrary array-like data
 structures, not just tabular data.

 2. They're anchored in a database, not just in code, so that you
 can leverage them in queries.

 3. They can be used to curate external data structures and define
 record frames via LaminDB's records.

 To create a schema, at least one of the following parameters must
 be passed:

 * "features" - a list of "Feature" objects

 * "itype" - the identifier type, e.g., "Feature" or
 "bt.Gene.ensembl_gene_id"

 * "slots" - a dictionary mapping slots to "Schema" objects, e.g.,
 for an "AnnData", "{"obs": Schema(...), "var.T": Schema(...)}"

 * "is_type=True" - a *schema type* to group schemas, e.g.,
 "ProteinPanel"

 Parameters:
| * **features** -- "list[SQLRecord] | list[tuple[Feature, dict]] |
| None = None" Feature records, e.g., "[Feature(...), |
 Feature(...)]" or features with their config, e.g.,
 "[Feature(...).with_config(optional=True)]".

| * **slots** -- "dict[str, Schema] | None = None" A dictionary |
 mapping slot names to "Schema" objects to create a _composite_
 schema.

| * **name** -- "str | None = None" Name of the schema. |

| * **description** -- "str | None = None" Description of the |
 schema.

| * **itype** -- "str | None = None" Feature identifier type to |
 validate against, e.g., "ln.Feature" or
 "bt.Gene.ensembl_gene_id". Is automatically set to the type of
 the passed "features".

| * **type** -- "Schema | None = None" Define schema types like |
 "ln.Schema(name="ProteinPanel", is_type=True)".

 * **is_type** -- "bool = False" Whether this is a schema type.

| * **index** -- "Feature | None = None" Index analogous to a |
 "DataFrame" index. In "DataFrame" curation, validates
 "df.index". When stored in "Record", stored on "name" and must
 have "dtype=str".

| * **flexible** -- "bool | None = None" Whether to include any |
 feature of the same "itype" during validation & annotation. If
 "features" is passed, defaults to "False" so that, e.g.,
 additional columns of a "DataFrame" encountered during
 validation are disregarded. If "features" is not passed,
 defaults to "True".

| * **otype** -- "str | None = None" An object type to define the |
 structure of a composite schema, e.g., ""DataFrame"",
 ""AnnData"". Required when "slots" constrain dataset content.
 Optional when the only slot is "__external__", so the schema
 can validate metadata on any artifact.

| * **suffix** -- "str | None = None" A required artifact suffix |
 for schema-validated artifacts. Must be a canonical suffix as
 defined by "CanonicalSuffix", e.g., "".csv"", "".parquet"", or
 "".zarr"".

| * **dtype** -- "str | None = None" A "dtype" to assume for all |
 features in the schema (e.g., "num", float, int). Defaults to
 "None" if "itype" is "Feature". Otherwise to ""num"", e.g., if
 "itype" is "bt.Gene.ensembl_gene_id".

 * **minimal_set** -- "bool = True" Whether all passed features
 are required by default. See "optionals" for more-fine-grained
 control. If "False", all features are optional.

 * **maximal_set** -- "bool = False" Whether additional features
 are allowed.

 * **ordered_set** -- "bool = False" Whether features are
 required to be ordered.

| * **coerce** -- "bool | None = None" When "True", coerces every |
 column and the index during validation, including features
 that leave "coerce" unset. See "coerce".

| * **n_members** -- "int | None = None" A manual way of |
 specifying the number of features in the schema. Is inferred
 from "features" if passed.

| * **branch** -- "Branch | None = None" A branch. If "None", uses |
 the current branch.

| * **space** -- "Space | None = None" A space. If "None", uses |
 the current space.

 See also:

 "from_dataframe()"
 Validate & annotate a "DataFrame" with a schema.

 "from_anndata()"
 Validate & annotate an "AnnData" with a schema.

 "from_mudata()"
 Validate & annotate an "MuData" with a schema.

 "from_spatialdata()"
 Validate & annotate a "SpatialData" with a schema.

# Examples

 A schema with a single required feature:

 import lamindb as ln

 schema = ln.Schema([ln.Feature(name="required_feature", dtype=str).save()]).save()

 A schema that constrains feature identifiers to be a valid feature
 names:

 schema = ln.Schema(itype=ln.Feature)  # uses Feature.name as identifier type

 Or valid Ensembl gene ids:

 import bionty as bt

 schema = ln.Schema(itype=bt.Gene.ensembl_gene_id)

 A "flexible" schema that *requires* a single feature but *also*
 validates & annotates additional features with registered feature
 identifiers:

 schema = ln.Schema(
 [ln.Feature(name="required_feature", dtype=str).save()],
 itype=ln.Feature,
 flexible=True,
 ).save()

 Create a schema type to group schemas:

 protein_panel = ln.Schema(name="ProteinPanel", is_type=True).save()
 schema = ln.Schema(itype=bt.CellMarker, type=protein_panel).save()

 Validate the "index" of a "DataFrame":

 schema = ln.Schema(
 [ln.Feature(name="required_feature", dtype=str).save()],
 index=ln.Feature(name="sample", dtype=ln.ULabel).save(),
 ).save()

 Mark a feature as "optional":

 schema = ln.Schema([
 ln.Feature(name="required_feature", dtype=str).save(),
 ln.Feature(name="feature2", dtype=int).save().with_config(optional=True),
 ]).save()

 Setting "index" stores the index feature on the "name" field of
 "Record":

 sample_id = ln.Feature(name="sample_id", dtype=str).save()
 schema = ln.Schema(features=[ln.Feature(name="score", dtype=float).save()], index=sample_id).save()

 Parse & validate feature identifier values:

 schema = ln.Schema.from_values(
 adata.var["ensemble_id"],
 field=bt.Gene.ensembl_gene_id,
 organism="mouse",
 ).save()

 Create a schema from a "DataFrame":

 df = pd.DataFrame({"feat1": [1, 2], "feat2": [3.1, 4.2], "feat3": ["cond1", "cond2"]})
 schema = ln.Schema.from_dataframe(df)

 property members: QuerySet

 The feature objects underlying the schema.

 This is a non-empty set only if the schema object is composed by
 a set of feature objects.

 Unlike the many-to-many fields that relate the feature objects
 (".features", ".genes", ".proteins", etc.) the ".members"
 attribute returns an ordered "QuerySet" if the schema is saved
 or a "SQLRecordList" if the schema is unsaved.

 Feature schemas follow "_aux["af"]["2"]" when that value is a
 list of feature UIDs: named members first, then any member the
 list omits, in link-id order. Otherwise, and for non-feature
 members, order is the link row id.

| property dtype: str | None |

 The "dtype" for all features in the schema.

 This can only be a simple data type in case of a "Schema": see
 section Data types on the "Feature" page.

| property index: None | Feature |

 The feature configured to act as index.

 For "DataFrame" / "AnnData" schemas, validates row indices
 during curation. For record frame schemas, the index feature
 must have "dtype=str"; see "Record". The index feature
 automatically targets "name" (equivalent to "field="name"" for
 that feature). The schema must be saved before assigning
 "schema.index" (pass "index" to the constructor for unsaved
 schemas). Assignment only sets or clears the index marker; it
 does not add or remove schema members. On "save()", record
 frames migrate row keys between "name" and the link table when
 the index changes. If both differ for a data record, you are
 prompted to keep "Record.name" or the feature values; pass
 "index_name_conflict="keep_name"" or ""keep_feature"" to
 "save()" to skip the prompt.

 property slots: dict[str, lamindb.Schema]

 Slots.

 -[ Examples ]-

 # define composite schema
 anndata_schema = ln.Schema(
 name="mini_immuno_anndata_schema",
 otype="AnnData",
 slots={"obs": obs_schema, "var": var_schema},
 ).save()

 # access slots
 anndata_schema.slots
 #> {'obs': <Schema: obs_schema>, 'var': <Schema: var_schema>}

 property formats: FormatConstraints

 Format-specific constraints.

 Currently supports "zarr". Constraints are not part of the
 schema hash.

 Example:

 schema.formats.zarr = {
 "zarr_format": 3,
 "multiscales": {"scale": 2},
 }
 schema.save()

 property optionals: SchemaOptionals

 Manage optional features.

 -[ Example ]-

 # a schema with optional "sample_name"
 schema_optional_sample_name = ln.Schema(
 features=[
 ln.Feature(name="sample_id", dtype=str).save(),  # required
 ln.Feature(name="sample_name", dtype=str).save().with_config(optional=True),  # optional
 ],
 ).save()

 # raise ValidationError since `sample_id` is required
 ln.curators.DataFrameCurator(
 pd.DataFrame(
 {
 "sample_name": ["Sample 1", "Sample 2"],
 }
 ),
 schema=schema_optional_sample_name).validate()
 )

 # passes because an optional column is missing
 ln.curators.DataFrameCurator(
 pd.DataFrame(
 {
 "sample_id": ["sample1", "sample2"],
 }
 ),
 schema=schema_optional_sample_name).validate()
 )

 uid: str

 A universal id.

| name: str | None |

 A name.

| description: str | None |

 A description.

| n_members: int | None |

 Number of features in the schema. None for type-like schemas.

| coerce: bool | None |

 Whether values are coerced to the feature dtype during
 validation.

 A column is coerced when this is "True" or when that feature's
 "coerce" is "True", the same way pandera combines
 "DataFrameSchema.coerce" and "Column.coerce". "True" here
 coerces every column and the index, including features that
 leave "coerce" unset. A feature with "coerce=True" is coerced
 even when this is left unset. Either flag is enough.
 "Feature.coerce=False" does not disable schema-level coercion.

 For "int" and "float", coercion is lossless (""1"" and "1.0"
 become "int"; "1.1" does not) and does not change an existing
 integer or float width.

 "None" for type-like schemas.

| flexible: bool | None |

 Indicates how to handle validation and annotation in case
 features are not defined.

 -[ Examples ]-

 Make a rigid schema flexible:

 schema = ln.Schema.get(name="my_schema")
 schema.flexible = True
 schema.save()

 During schema creation:

 # if you're not passing features but just defining the itype, defaults to flexible = True
 schema = ln.Schema(itype=ln.Feature).save()
 # schema.flexible is True

 # if you're passing features, defaults to flexible = False
 schema = ln.Schema(
 features=[ln.Feature(name="my_required_feature", dtype=int).save()],
 )
 # schema.flexible is False

 # you can also validate & annotate features in addition to those that you're explicitly defining:
 schema = ln.Schema(
 features=[ln.Feature(name="my_required_feature", dtype=int).save()],
 flexible=True,
 )
 # schema.flexible is True

| itype: str | None |

 A field of a registry that stores feature identifier types,
 e.g., "'Feature.name'" or "'bionty.Gene.ensembl_gene_id'".
 Defaults to the default name field if a registry is passed
 (passing "Feature" would result in "Feature.name").

 Depending on "itype", ".members" stores, e.g., "Feature" or
 "bionty.Gene" records.

| otype: str | None |

 Default Python object type, e.g., DataFrame, AnnData.

| suffix: str | None |

 A required canonical suffix for artifacts validated with this
 schema.

 Like "suffix", this uses canonical storage-format suffixes from
 "CanonicalSuffix", such as "".csv"", "".parquet"", or "".zarr"".

 If set, "save()" raises a "ValidationError" when an artifact's
 suffix does not match.

 See "formats" for additional format-specific constraints.

| hash: str | None |

 A hash of the set of feature identifiers.

 For a composite schema, the hash of hashes.

 minimal_set: bool

 Whether all passed features are to be considered required by
 default (default "True").

 Note that features that are explicitly marked as "optional" via
 "feature.with_config(optional=True)" are **not** required even
 if this "minimal_set" is true.

 ordered_set: bool

 Whether features are required to be ordered (default "False").

 maximal_set: bool

 Whether all features present in the dataset must be in the
 schema (default "False").

 If "False", additional features are allowed to be present in the
 dataset.

 If "True", no additional features are allowed to be present in
 the dataset.

| type: Schema | None |

 Type of schema.

 Allows to group schemas by type, e.g., all meassurements
 evaluating gene expression vs. protein expression vs. multi
 modal.

 You can define types via "ln.Schema(name="ProteinPanel",
 is_type=True)".

 Here are a few more examples for type names:
 "'ExpressionPanel'", "'ProteinPanel'", "'Multimodal'",
 "'Metadata'", "'Embedding'".

 components: RelatedManager[Schema]

 Components of this schema ← "composites".

 features: RelatedManager[Feature]

 The features contained in the schema ← "schemas".

 schemas: RelatedManager[Schema]

 Schemas of this type (can only be non-empty if "is_type" is
 "True").

 composites: RelatedManager[Schema]

 The composite schemas that contains this schema as a component ←
 "components".

 For example, an "AnnData" composes multiple schemas:
 "var[DataFrameT]", "obs[DataFrame]", "obsm[Array]", "uns[dict]",
 etc.

 validated_artifacts: Artifact

 The artifacts that were validated against this schema ←
 "schema".

 artifacts: RelatedManager[Artifact]

 The artifacts with an inferred schema that matches this schema ←
 "schemas".

 validated_collections

 records: RelatedManager[Record]

 Records that were annotated with this schema ← "schema".

 projects: RelatedManager[Project]

 Linked projects ← "schemas".

 ablocks: RelatedManager[SchemaBlock]

 Attached blocks ← "schema".

 classmethod from_values(values, field=FieldAttr(Feature.name), dtype=None, name=None, mute=False, organism=None, source=None, raise_validation_error=True)

 Create feature set for validated features.

 Parameters:
 * **values** (lamindb.base.types.ListLike) -- A list of
 values, like feature names or ids.

 * **field** (lamindb.base.types.FieldAttr, default:
 "FieldAttr(Feature.name)") -- The field of a reference
 registry to map values.

| * **dtype** ("str" | "None", default: "None") -- The simple |
 dtype. Defaults to "None" if reference registry is
 "Feature", defaults to ""float"" otherwise.

| * **name** ("str" | "None", default: "None") -- A name. |

| * **organism** ("SQLRecord" | "str" | "None", default: |
 "None") -- An organism to resolve gene mapping.

| * **source** ("SQLRecord" | "None", default: "None") -- A |
 public ontology to resolve feature identifier mapping.

 * **raise_validation_error** ("bool", default: "True") --
 Whether to raise a validation error if some values are not
 valid.

 Raises:
 **ValidationError** -- If some values are not valid.

 Return type:
 lamindb.Schema

 -[ Example ]-

 import lamindb as ln
 import bionty as bt

 features = [ln.Feature(name=feat, dtype="str").save() for feat in ["feat11", "feat21"]]
 schema = ln.Schema.from_values(features)

 genes = ["ENSG00000139618", "ENSG00000198786"]
 schema = ln.Schema.from_values(features, bt.Gene.ensembl_gene_id, "float")

 classmethod from_dataframe(df, field=FieldAttr(Feature.name), name=None, mute=False, organism=None, source=None)

 Create schema for valid columns.

 Return type:
| Schema | None |

 query_schemas(depth=None)

 Query schemas of sub types.

 While ".schemas" retrieves the schemas with the current type,
 this method also retrieves sub types and the schemas with sub
 types of the current type.

 Parameters:
| **depth** ("int" | "None", default: "None") -- How many type |
 hops to follow. "None" walks the whole subtree. "1" returns
 only the direct schemas of this type.

 Return type:
 "QuerySet"

 save(*args, **kwargs)

 Save schema.

 When the schema hash changes because "schema.index" was set or
 unset on a record frame schema, index values are migrated
 between "name" and the link table in bulk. If "Record.name" and
 the link-table index value both exist and differ, you are
 prompted to keep "Record.name" ("y") or use the feature values
 ("n"); pass "index_name_conflict="keep_name"" or
 ""keep_feature"" to skip the prompt.

 UX example:

 >>> schema.save()
 ! you updated the schema hash and might invalidate datasets...
 1 data record(s) have both Record.name and 'name' values that differ.
 record frame My samples 2025-05 (Kzpu3Xo8g7xy), data record 21977: Record.name='schmidt22_perturbseq', name='sample-001'
 Keep Record.name (y) or use feature values (n)? y

 The prompt only appears when both values exist and differ;
 matching values migrate silently.

 Return type:
 lamindb.Schema

 add(features, optional=True)

 Add one or multiple features to the schema.

 Parameters:
| * **features** ("Feature" | "list"["Feature"]) -- The feature |
 or list of features to add.

 * **optional** ("bool", default: "True") -- Whether the
 feature to be added is optional.

 Return type:
 "None"

 remove(features)

 Remove one or multiple features from the schema.

 Return type:
 "None"

 describe(include=None, n_max_features=None)

 Describe schema.

 Parameters:
 * **return_str** ("bool", default: "False") -- Return a
 string instead of printing.

| * **include** ("None" | "Literal"["'comments'", "'hidden'"], |
 default: "None") -- Pass ""hidden"" to list features
 omitted by default. Same idea as
 "to_dataframe(include=...)": the extra content stays out of
 the default view until requested.

 Return type:
| "None" | "str" |

 Feature rows follow "_aux["af"]["2"]" when that list is present,
 and omit uids in "_aux["af"]["4"]" unless "include="hidden"".
 The index feature stays visible. The feature header notes how
 many features are hidden. Neither list is applied to any other
 display.