serp-molt
A pydantic v2 drop-in for the Serpentine subset: declare a model as an
annotated class deriving BaseModel, and get keyword construction,
model_validate with pydantic's lax coercion table, model_dump /
model_dump_json, Field() constraints, nested models, @field_validator /
@model_validator, @field_serializer / @model_serializer /
@computed_field, model_config, pydantic-shaped ValidationError text and
JSON Schema.
Models are not reflection: the compiler's build-time field table is exposed
through the fields_of / fields_json / fields_build intrinsics (and the
hook table through hooks_json / run_hook / run_hook_self /
run_hook_val / run_hook_get / run_hook_info), and
because inheritance is check-time flattening, the single fields_of(self) in
BaseModel.model_dump is monomorphized against each subclass's own fields.
API
from serp_molt import (BaseModel, ConfigDict, Field, ValidationError,
field_validator, model_validator)
class User(BaseModel):
id: int
name: str = Field(default="anon", min_length=2, max_length=10)
age: Annotated[int, Field(ge=0, le=150)] = 0
tags: list[str] = Field(default_factory=list)
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
@field_validator("name")
@classmethod
def lower_name(cls, v: PyVal) -> PyVal:
return str(v).lower()
@model_validator(mode="after")
def check(self: Ref[User]) -> None:
if self.age > 150:
raise ValueError("too old")
u = User.model_validate({"id": "7", "name": "Ada"}) # str -> int coercion
u.model_dump_json()
- Class surface:
model_validate(obj),model_validate_json(text),model_construct(values)(no validation),model_json_schema(),model_fields(),model_validate_errors(obj), plus v1parse_obj,parse_raw,schema. - Instance surface:
model_dump(by_alias=False, exclude_none=False),model_dump_json(...),model_copy(update),__str__(id=1 name='a'),__repr__/__eq__from@dataclass, plus v1dict()/json(). Field():default(positional or keyword),default_factory,gt,ge,lt,le,multiple_of,min_length,max_length,pattern,alias,validation_alias,serialization_alias,title,description. Usable either in default position or insideAnnotated[T, Field(...)].- Field types:
int,float,bool,str,bytes,T | None,list[T],dict[str, T],set[T],tuple[...],Enum/IntEnum/StrEnum, general unions (int | str), nested models, models nested in lists/dicts, and generic models (class Page(BaseModel, Generic[T]), validated asPage[int]).PyValfields accept anything. - Coercion (lax mode):
"7"→int,"1.5"/int→float,36.0→intwhen integral,"true"/"yes"/"on"/"1"→bool, numbers→stris not done (pydantic v2 behaviour). - Validators:
@field_validator(*names, mode="before"|"after")(a@classmethodtaking onePyVal) and@model_validator(mode="before")(raw input mapping in, mapping out) /mode="after"(aRef[Self]method).beforefield validators see the raw input,afterones see the coerced value and their result is re-checked against the field type. RaisingValueErrorinside one becomes avalue_errorentry in the accumulatedValidationError. Validators are inherited, and a subclass redefining the method replaces the base hook.Annotated[T, BeforeValidator(f)]andAnnotated[T, AfterValidator(f)]also work, wherefis a module-level(v: PyVal) -> PyValfunction. A two-argument validator(cls, v: PyVal, info: PyVal)receives aValidationInfo-shaped mapping (info["data"],info["field_name"]). - Serializers:
@field_serializer(*names)(a method taking the field's value),@model_serializer(a method taking the whole dumped mapping and returning anyPyVal) and@computed_field+@property(added tomodel_dumpoutput) all run on the dump path. - Constrained aliases:
PositiveInt,NonNegativeInt,NegativeInt,NonPositiveInt, the four*Floatcounterparts, plusAnyUrl,AnyHttpUrl,HttpUrlandEmailStr(pattern-based, noemail-validator). - Markers:
Annotated[T, Json()]parses the field's raw string as JSON before validatingT;Annotated[T, SkipValidation()]skips type checks,str_*config andField()constraints.PydanticCustomError("type: msg")raised in a validator becomes an entry with thattype/msg. - Root models: a model whose only field is named
rootvalidates from (and dumps back to) the bare value, standing in forRootModel[T]. model_fields_set: a classmethod —User.model_fields_set(mapping)returns the field names the input supplies explicitly.model_config = ConfigDict(...):extra="ignore"|"forbid",populate_by_name,str_strip_whitespace,str_to_lower,str_to_upper,str_min_length,str_max_length,frozen,validate_assignment,alias_generator. Inherited unless overridden.- Mutation: field assignment goes through
BaseModel.__setattr__, sofrozen=Trueraises afrozen_instanceValidationErrorandvalidate_assignment=Truere-validates the written value against the field's type,str_*config andField()constraints. - Construction:
User(id=1, name="ada"),User(**mapping)andUser(**{"id": 1})all work.**construction uses strict field types (no coercion) — usemodel_validatefor lax input. - Errors: every failure is collected, not just the first.
str(e)matches pydantic's multi-lineN validation errors for Model/loc/msg [type=..., input_value=..., input_type=...]layout, and errortypestrings (missing,int_parsing,greater_than,string_too_short,string_pattern_mismatch, …) are pydantic's.
Caps (loud errors or documented divergences)
ValidationErrorcarries only its message; the structured error list comes fromModel.model_validate_errors(obj)instead ofe.errors(), because a Serpentine exception cannot carry a typed payload. (model_validate_errorsreports field errors only — it does not run validators.)locis alist, not a tuple.str(e)omits pydantic's trailingerrors.pydantic.devURL line.- Validators take one
PyVal, or two forValidationInfo(info["data"]/info["field_name"]/info["context"]as a mapping, not an object).mode="wrap"andmode="plain"andWrapValidatorare not implemented. - An
after-model validator's error reports the input mapping asinput_value, where pydantic reports the model instance. extra="allow"/__pydantic_extra__are absent.model_fields_setis a classmethod over the input mapping, not per-instance state.alias_generatorapplies to a model's own fields only (nested models use their own config), and hashable frozen models (__hash__) are not supported.Enumfields take (and dump) the member value, never anEnuminstance;use_enum_valuesis therefore moot.set[T]fields validate from a JSON list and dump back to a list, deduped in input order.- A union field (
int | str) is validated by the input's runtime kind, not by pydantic's smart-union order, and a failure reports oneunion_typeerror instead of one per member. A union of two models (Cat | Dog) is a compile error (SE201) because both arrive as JSON objects; discriminated unions (Field(discriminator=...)) are not implemented. - Generic models (
class Page(BaseModel, Generic[T])) are validated through an explicit instantiation —Page[int].model_validate(d); a barePage.model_validate(d)is a compile error. - No
Decimal,TypeAdapter,create_model,@validate_call, orBaseSettings.RootModel[T]is spelled as a single-root-field model. date/datetime/timedelta/UUIDfields are supported (ISO text in and out,timedeltaas seconds) but are naive only — serp-datetime has no timezones.AnyUrl/HttpUrl/EmailStrare pattern-validatedstraliases, not parsed URL objects;SecretStr/Base64Bytesare absent.@computed_fieldvalues appear inmodel_dump, and inmodel_json_schema("serialization")asreadOnlyproperties (pydantic'smode="serialization"); the default validation-mode schema omits them.- JSON Schema covers
type/title/description/default/anyOf/$defs- the constraint keywords,
formatfor temporal/UUID fields andjson_schema_extra;mode=is positional-or-keyword with"validation"/"serialization", andby_alias=defaults toTrue.
- the constraint keywords,
Install
serp add serp-molt
pip install serp-molt
Metadata
Release files for serp-molt 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| serp_molt-0.7.0.tar.gz | 27.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| serp_molt-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.2 kB
Release files / serp_molt-0.7.0.tar.gz
| Download URL | serp_molt-0.7.0.tar.gz |
|---|---|
| Size | 27.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
581814b3b5616ea262f4471803c6a2d00e5063e15c60230678f82f2a97d1b44b
|
|
BLAKE2b-256 checksum How to use checksums |
0132aea17d34fad0567d44697b79a24b7791cb63fc79b29b43fb0d34cddd7eeb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / serp_molt-0.7.0-py3-none-any.whl
| Download URL | serp_molt-0.7.0-py3-none-any.whl |
|---|---|
| Size | 20.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d38f50d756d56e7b7a866c3287d84984ca232463f3b008602ca4406ad6b46b0b
|
|
BLAKE2b-256 checksum How to use checksums |
abde8843f007ac35adfc5aa0d6e149c5ad4396e2792e9195843bde8cebd3f212
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log