hexdoc.model
1__all__ = [ 2 "DEFAULT_CONFIG", 3 "Color", 4 "HexdocModel", 5 "HexdocSettings", 6 "HexdocTypeAdapter", 7 "IDModel", 8 "InlineItemModel", 9 "InlineModel", 10 "InternallyTaggedUnion", 11 "NoValue", 12 "NoValueType", 13 "ResourceModel", 14 "StripHiddenModel", 15 "TagValue", 16 "TypeTaggedTemplate", 17 "TypeTaggedUnion", 18 "ValidationContextModel", 19 "init_context", 20] 21 22from .base import ( 23 DEFAULT_CONFIG, 24 HexdocModel, 25 HexdocSettings, 26 HexdocTypeAdapter, 27 ValidationContextModel, 28 init_context, 29) 30from .id import IDModel, ResourceModel 31from .inline import InlineItemModel, InlineModel 32from .strip_hidden import StripHiddenModel 33from .tagged_union import ( 34 InternallyTaggedUnion, 35 NoValue, 36 NoValueType, 37 TagValue, 38 TypeTaggedTemplate, 39 TypeTaggedUnion, 40) 41from .types import Color
13@dataclass( 14 frozen=True, 15 config=DEFAULT_CONFIG 16 | json_schema_extra_config( 17 type_str, 18 inherited, 19 pattern=r"(#|0x)?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})", 20 ), 21) 22class Color: 23 """Represents a hexadecimal color. 24 25 Inputs are coerced to lowercase `rrggbb`. Raises ValueError on invalid input. 26 27 Valid formats, all of which would be converted to `0099ff`: 28 - `"#0099FF"` 29 - `"#0099ff"` 30 - `"#09F"` 31 - `"#09f"` 32 - `"0099FF"` 33 - `"0099ff"` 34 - `"09F"` 35 - `"09f"` 36 - `0x0099ff` 37 """ 38 39 value: str 40 41 @model_validator(mode="before") 42 def _pre_root(cls, value: Any): 43 if isinstance(value, (str, int)): 44 return {"value": value} 45 return value 46 47 @field_validator("value", mode="before") 48 def _check_value(cls, value: Any) -> str: 49 # type check 50 match value: 51 case str(): 52 value = value.removeprefix("#").lower() 53 case int(): 54 # int to hex string 55 value = f"{value:0>6x}" 56 case _: 57 raise TypeError(f"Expected str or int, got {type(value)}") 58 59 # 012 -> 001122 60 if len(value) == 3: 61 value = "".join(c + c for c in value) 62 63 # length and character check 64 if len(value) != 6 or any(c not in string.hexdigits for c in value): 65 raise ValueError(f"invalid color code: {value}") 66 67 return value
Represents a hexadecimal color.
Inputs are coerced to lowercase rrggbb
. Raises ValueError on invalid input.
Valid formats, all of which would be converted to 0099ff
:
"#0099FF"
"#0099ff"
"#09F"
"#09f"
"0099FF"
"0099ff"
"09F"
"09f"
0x0099ff
41@dataclass_transform() 42class HexdocModel(BaseModel): 43 """Base class for all Pydantic models in hexdoc. 44 45 Sets the default model config, and overrides __init__ to allow using the 46 `init_context` context manager to set validation context for constructors. 47 """ 48 49 model_config = DEFAULT_CONFIG 50 """@private""" 51 52 __hexdoc_before_validator__: ClassVar[ModelBeforeValidator | None] = None 53 54 def __init__(__pydantic_self__, **data: Any) -> None: # type: ignore 55 __tracebackhide__ = True 56 __pydantic_self__.__pydantic_validator__.validate_python( 57 data, 58 self_instance=__pydantic_self__, 59 context=_init_context_var.get(), 60 ) 61 62 __init__.__pydantic_base_init__ = True # type: ignore 63 64 @model_validator(mode="before") 65 @classmethod 66 def _call_hexdoc_before_validator(cls, value: Any, info: ValidationInfo): 67 # allow json schema field in all models 68 if isinstance(value, dict): 69 value = cast(dict[Any, Any], value) 70 value.pop("$schema", None) 71 if cls.__hexdoc_before_validator__: 72 return cls.__hexdoc_before_validator__(cls, value, info) 73 return value
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.
54 def __init__(__pydantic_self__, **data: Any) -> None: # type: ignore 55 __tracebackhide__ = True 56 __pydantic_self__.__pydantic_validator__.validate_python( 57 data, 58 self_instance=__pydantic_self__, 59 context=_init_context_var.get(), 60 )
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
validated to form a valid model.
self
is explicitly positional-only to allow self
as a field name.
76class HexdocSettings(BaseSettings): 77 model_config = SettingsConfigDict( 78 env_file=".env", 79 extra="allow", 80 ) 81 82 @classmethod 83 def model_getenv(cls, defaults: Any = None): 84 return cls.model_validate(defaults or {})
Base class for settings, allowing values to be overridden by environment variables.
This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose), Heroku and any 12 factor app design.
All the below attributes can be set via model_config
.
Args:
_case_sensitive: Whether environment and CLI variable names should be read with case-sensitivity.
Defaults to None
.
_nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields.
Defaults to False
.
_env_prefix: Prefix for all environment variables. Defaults to None
.
_env_file: The env file(s) to load settings values from. Defaults to Path('')
, which
means that the value from model_config['env_file']
should be used. You can also pass
None
to indicate that environment variables should not be loaded from an env file.
_env_file_encoding: The env file encoding, e.g. 'latin-1'
. Defaults to None
.
_env_ignore_empty: Ignore environment variables where the value is an empty string. Default to False
.
_env_nested_delimiter: The nested env values delimiter. Defaults to None
.
_env_nested_max_split: The nested env values maximum nesting. Defaults to None
, which means no limit.
_env_parse_none_str: The env string value that should be parsed (e.g. "null", "void", "None", etc.)
into None
type(None). Defaults to None
type(None), which means no parsing should occur.
_env_parse_enums: Parse enum field names to values. Defaults to None.
, which means no parsing should occur.
_cli_prog_name: The CLI program name to display in help text. Defaults to None
if _cli_parse_args is None
.
Otherwse, defaults to sys.argv[0].
_cli_parse_args: The list of CLI arguments to parse. Defaults to None.
If set to True
, defaults to sys.argv[1:].
_cli_settings_source: Override the default CLI settings source with a user defined instance. Defaults to None.
_cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into
None
type(None). Defaults to _env_parse_none_str value if set. Otherwise, defaults to "null" if
_cli_avoid_json is False
, and "None" if _cli_avoid_json is True
.
_cli_hide_none_type: Hide None
values in CLI help text. Defaults to False
.
_cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to False
.
_cli_enforce_required: Enforce required fields at the CLI. Defaults to False
.
_cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions.
Defaults to False
.
_cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs.
Defaults to True
.
_cli_prefix: The root parser command line arguments prefix. Defaults to "".
_cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'.
_cli_implicit_flags: Whether bool
fields should be implicitly converted into CLI boolean flags.
(e.g. --flag, --no-flag). Defaults to False
.
_cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to False
.
_cli_kebab_case: CLI args use kebab case. Defaults to False
.
_secrets_dir: The secret files directory or a sequence of directories. Defaults to None
.
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
23class IDModel(HexdocModel): 24 id: SkipJsonSchema[ResourceLocation] 25 resource_dir: SkipJsonSchema[PathResourceDir] 26 27 @classmethod 28 def load( 29 cls, 30 resource_dir: PathResourceDir, 31 id: ResourceLocation, 32 data: JSONDict, 33 context: dict[str, Any], 34 ) -> Self: 35 logger.log(TRACE, f"Load {cls} at {id}") 36 return cls.model_validate( 37 data | {"id": id, "resource_dir": resource_dir}, 38 context=context, 39 )
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.
27 @classmethod 28 def load( 29 cls, 30 resource_dir: PathResourceDir, 31 id: ResourceLocation, 32 data: JSONDict, 33 context: dict[str, Any], 34 ) -> Self: 35 logger.log(TRACE, f"Load {cls} at {id}") 36 return cls.model_validate( 37 data | {"id": id, "resource_dir": resource_dir}, 38 context=context, 39 )
70@dataclass_transform() 71class InlineItemModel(BaseInlineModel, ABC): 72 @classmethod 73 def _id_type(cls): 74 return ItemStack 75 76 @classmethod 77 @abstractmethod 78 def load_id(cls, item: ItemStack, context: dict[str, Any]) -> Any: ... 79 80 @model_validator(mode="wrap") 81 @classmethod 82 def _wrap_root_load_from_id( 83 cls, 84 value: Any, 85 handler: ModelWrapValidatorHandler[Self], 86 info: ValidationInfo, 87 ) -> Self: 88 """Loads the recipe from json if the actual value is a resource location str. 89 90 Uses a wrap validator so we load the file *before* resolving the tagged union. 91 """ 92 # if necessary, convert the id to a ItemStack 93 match value: 94 case str(): 95 item = ItemStack.from_str(value) 96 case ItemStack() as item: 97 pass 98 case ResourceLocation() as id: 99 item = ItemStack(namespace=id.namespace, path=id.path) 100 case _: 101 return handler(value) 102 103 # load the data 104 assert info.context is not None 105 return handler(cls.load_id(item, info.context))
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.
34@dataclass_transform() 35class InlineModel(BaseInlineModel, ABC): 36 @classmethod 37 def _id_type(cls): 38 return ResourceLocation 39 40 @classmethod 41 @abstractmethod 42 def load_id(cls, id: ResourceLocation, context: dict[str, Any]) -> Any: ... 43 44 @model_validator(mode="wrap") 45 @classmethod 46 def _wrap_root_load_from_id( 47 cls, 48 value: Any, 49 handler: ModelWrapValidatorHandler[Self], 50 info: ValidationInfo, 51 ) -> Self: 52 """Loads the recipe from json if the actual value is a resource location str. 53 54 Uses a wrap validator so we load the file *before* resolving the tagged union. 55 """ 56 # if necessary, convert the id to a ResourceLocation 57 match value: 58 case str(): 59 id = ResourceLocation.from_str(value) 60 case ResourceLocation() as id: 61 pass 62 case _: 63 return handler(value) 64 65 # load the data 66 assert info.context is not None 67 return handler(cls.load_id(id, info.context))
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.
44class InternallyTaggedUnion(HexdocModel): 45 """Implements [internally tagged unions](https://serde.rs/enum-representations.html#internally-tagged) 46 using the [Registry pattern](https://charlesreid1.github.io/python-patterns-the-registry.html). 47 48 To ensure your subtypes are loaded even if they're not imported by any file, add a 49 Pluggy hook implementation for `hexdoc_load_tagged_unions() -> list[Package]`. 50 51 Subclasses MUST NOT be generic unless they provide a default value for all 52 `__init_subclass__` arguments. See pydantic/7171 for more info. 53 54 Args: 55 key: The dict key for the internal tag. If None, the parent's value is used. 56 value: The expected tag value for this class. Should be None for types which 57 shouldn't be instantiated (eg. abstract classes). 58 """ 59 60 # inherited 61 _tag_key: ClassVar[str | None] = None 62 _tag_value: ClassVar[TagValue | None] = None 63 64 # per-class 65 __all_subtypes: ClassVar[set[type[Self]]] 66 __concrete_subtypes: ClassVar[defaultdict[TagValue, set[type[Self]]]] 67 68 def __init_subclass__( 69 cls, 70 *, 71 key: str | InheritType | None = Inherit, 72 value: TagValue | InheritType | None = Inherit, 73 **kwargs: Unpack[ConfigDict], 74 ): 75 super().__init_subclass__(**kwargs) 76 77 # inherited data 78 if key is not Inherit: 79 cls._tag_key = key 80 if value is not Inherit: 81 cls._tag_value = value 82 83 # don't bother with rest of init if it's not part of a union 84 if cls._tag_key is None: 85 if cls._tag_value is None: 86 return 87 raise ValueError( 88 f"Expected value=None for {cls} with key=None, got {value}" 89 ) 90 91 # per-class data and lookups 92 cls.__all_subtypes = set() 93 cls.__concrete_subtypes = defaultdict(set) 94 95 # add to all the parents 96 for supertype in cls._supertypes(): 97 supertype.__all_subtypes.add(cls) 98 if cls._tag_value is not None: 99 supertype.__concrete_subtypes[cls._tag_value].add(cls) 100 101 @classmethod 102 def _tag_key_or_raise(cls) -> str: 103 if cls._tag_key is None: 104 raise NotImplementedError 105 return cls._tag_key 106 107 @classmethod 108 def _supertypes(cls) -> Generator[type[InternallyTaggedUnion], None, None]: 109 tag_key = cls._tag_key_or_raise() 110 111 # we consider a type to be its own supertype/subtype 112 yield cls 113 114 # recursively yield bases 115 # stop when we reach a non-union or a type with a different key (or no key) 116 for base in cls.__bases__: 117 if issubclass(base, InternallyTaggedUnion) and base._tag_key == tag_key: 118 yield from base._supertypes() 119 120 @model_validator(mode="wrap") 121 @classmethod 122 def _resolve_from_dict( 123 cls, 124 value: Any, 125 handler: ModelWrapValidatorHandler[Self], 126 info: ValidationInfo, 127 ) -> Self: 128 pm = PluginManager.of(info) 129 130 # load plugins from entry points 131 global _is_loaded 132 if not _is_loaded: 133 more_itertools.consume(pm.load_tagged_unions()) 134 _is_loaded = True 135 136 # do this early so we know it's part of a union before returning anything 137 tag_key = cls._tag_key_or_raise() 138 139 # if it's already instantiated, just return it; otherwise ensure it's a dict 140 match value: 141 case InternallyTaggedUnion() if isinstance(value, cls): 142 return value 143 case dict() if _RESOLVED not in value: 144 data: dict[str, Any] = value 145 data[_RESOLVED] = True 146 case _: 147 return handler(value) 148 149 # tag value, eg. "minecraft:crafting_shaped" 150 tag_value = data.get(tag_key, NoValue) 151 152 # list of matching types, eg. [ShapedCraftingRecipe, ModConditionalShapedCraftingRecipe] 153 tag_types = cls.__concrete_subtypes.get(tag_value) 154 if tag_types is None: 155 raise TypeError(f"Unhandled tag: {tag_key}={tag_value} for {cls}: {data}") 156 157 # try all the types 158 exceptions: list[InitErrorDetails] = [] 159 matches: dict[type[Self], Self] = {} 160 161 for inner_type in tag_types: 162 try: 163 matches[inner_type] = inner_type.model_validate( 164 data, context=info.context 165 ) 166 except Exception as e: 167 exceptions.append( 168 InitErrorDetails( 169 type=PydanticCustomError( 170 "TaggedUnionMatchError", 171 "{exception_class}: {exception}", 172 { 173 "exception_class": e.__class__.__name__, 174 "exception": str(e), 175 }, 176 ), 177 loc=( 178 cls.__name__, 179 inner_type.__name__, 180 ), 181 input=data, 182 ) 183 ) 184 185 # ensure we only matched one 186 match len(matches): 187 case 1: 188 return matches.popitem()[1] 189 case x if x > 1: 190 ambiguous_types = ", ".join(str(t) for t in matches.keys()) 191 reason = f"Ambiguous union match: {ambiguous_types}" 192 case _: 193 reason = "No match found" 194 195 # something went wrong, raise an exception 196 error = PydanticCustomError( 197 "TaggedUnionMatchErrorGroup", 198 ( 199 "Failed to match tagged union {class_name}: {reason}\n" 200 " Tag: {tag_key}={tag_value}\n" 201 " Types: {types}\n" 202 " Data: {data}" 203 ), 204 { 205 "class_name": str(cls), 206 "reason": reason, 207 "tag_key": cls._tag_key, 208 "tag_value": tag_value, 209 "types": ", ".join(str(t) for t in tag_types), 210 "data": repr(data), 211 }, 212 ) 213 214 if exceptions: 215 if _RESOLVED in data: 216 data.pop(_RESOLVED) # avoid interfering with other types 217 exceptions.insert( 218 0, 219 InitErrorDetails( 220 type=error, 221 loc=(cls.__name__,), 222 input=data, 223 ), 224 ) 225 raise ValidationError.from_exception_data( 226 "TaggedUnionMatchError", exceptions 227 ) 228 229 raise RuntimeError(str(error)) 230 231 @model_validator(mode="before") 232 def _pop_temporary_keys(cls, value: dict[Any, Any] | Any): 233 if isinstance(value, dict) and _RESOLVED in value: 234 # copy because this validator may be called multiple times 235 # eg. two types with the same key 236 value = value.copy() 237 value.pop(_RESOLVED) 238 assert value.pop(cls._tag_key, NoValue) == cls._tag_value 239 return value 240 241 @classmethod 242 @override 243 def __get_pydantic_json_schema__( 244 cls, 245 core_schema: cs.CoreSchema, 246 handler: GetJsonSchemaHandler, 247 ) -> JsonSchemaValue: 248 base_schema = handler.resolve_ref_schema( 249 super().__get_pydantic_json_schema__(core_schema, handler) 250 ) 251 252 if cls._tag_key is None: 253 return base_schema 254 255 properties = base_schema.setdefault("properties", {}) 256 257 properties[cls._tag_key] = handler(cs.literal_schema([cls._tag_value])) 258 base_schema.setdefault("required", []).append(cls._tag_key) 259 260 if cls._tag_value is not None: 261 return base_schema 262 263 subtypes = { 264 subtype 265 for subtypes in cls.__concrete_subtypes.values() 266 for subtype in subtypes 267 if subtype is not cls 268 } 269 if not subtypes: 270 return base_schema 271 272 union_schema = cs.union_schema( 273 [subtype.__pydantic_core_schema__ for subtype in subtypes], 274 ) 275 json_schema = handler.resolve_ref_schema(handler(union_schema)) 276 277 if any_of := json_schema.get("anyOf"): 278 other_schema = base_schema | { 279 "additionalProperties": True, 280 "properties": ( 281 properties 282 | { 283 cls._tag_key: { 284 "allOf": [ 285 handler(cls._tag_value_schema), 286 { 287 "not": handler( 288 cs.union_schema( 289 [ 290 cs.literal_schema([subtype._tag_value]) 291 for subtype in subtypes 292 if subtype._tag_value 293 not in {None, NoValue} 294 ] 295 ) 296 ) 297 }, 298 ], 299 }, 300 } 301 ), 302 } 303 any_of.append(other_schema) 304 305 return json_schema 306 307 @classproperty 308 @classmethod 309 def _tag_value_type(cls) -> type[Any]: 310 return str 311 312 @classproperty 313 @classmethod 314 def _tag_value_schema(cls): 315 return TypeAdapter(cls._tag_value_type).core_schema
Implements internally tagged unions using the Registry pattern.
To ensure your subtypes are loaded even if they're not imported by any file, add a
Pluggy hook implementation for hexdoc_load_tagged_unions() -> list[Package]
.
Subclasses MUST NOT be generic unless they provide a default value for all
__init_subclass__
arguments. See pydantic/7171 for more info.
Args: key: The dict key for the internal tag. If None, the parent's value is used. value: The expected tag value for this class. Should be None for types which shouldn't be instantiated (eg. abstract classes).
8class NoValueType(Enum): 9 """Type of NoValue, a singleton representing the value of a nonexistent dict key.""" 10 11 _token = 0 12 13 def __str__(self): 14 return "NoValue" 15 16 def __bool__(self) -> Literal[False]: 17 return False
Type of NoValue, a singleton representing the value of a nonexistent dict key.
42class ResourceModel(IDModel, InlineModel, ABC): 43 @classmethod 44 def load_id(cls, id: ResourceLocation, context: dict[str, Any]): 45 loader = ModResourceLoader.of(context) 46 resource_dir, data = cls.load_resource(id, loader) 47 return cls.load(resource_dir, id, data, context) 48 49 @classmethod 50 @abstractmethod 51 def load_resource( 52 cls, 53 id: ResourceLocation, 54 loader: ModResourceLoader, 55 ) -> tuple[PathResourceDir, JSONDict]: ...
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.
20@dataclass_transform() 21class StripHiddenModel(HexdocModel): 22 """Base model which removes all keys starting with _ before validation.""" 23 24 model_config = DEFAULT_CONFIG | ConfigDict( 25 json_schema_extra=_json_schema_extra, 26 ) 27 28 @model_validator(mode="before") 29 def _pre_root_strip_hidden(cls, values: dict[Any, Any] | Any) -> Any: 30 if not isinstance(values, dict): 31 return values 32 33 return { 34 key: value 35 for key, value in values.items() 36 if not (isinstance(key, str) and (key.startswith("_") or key == "$schema")) 37 }
Base model which removes all keys starting with _ before validation.
347class TypeTaggedTemplate(TypeTaggedUnion, ABC, type=None): 348 __template_id: ClassVar[ResourceLocation] 349 350 def __init_subclass__( 351 cls, 352 *, 353 type: str | InheritType | None = Inherit, 354 template_type: str | None = None, 355 **kwargs: Unpack[ConfigDict], 356 ) -> None: 357 super().__init_subclass__(type=type, **kwargs) 358 359 # jinja template path 360 if template_type is not None: 361 template_id = ResourceLocation.from_str(template_type) 362 else: 363 template_id = cls.type 364 365 if template_id: 366 cls.__template_id = template_id 367 368 @classproperty 369 @classmethod 370 @abstractmethod 371 def template(cls) -> str: 372 """Returns the Jinja template path for this class without any file extension. 373 374 For example, return `"pages/{path}"`, not `"pages/{path}.html.jinja"`. 375 """ 376 377 @classproperty 378 @classmethod 379 def template_id(cls): 380 return cls.__template_id
Implements internally tagged unions using the Registry pattern.
To ensure your subtypes are loaded even if they're not imported by any file, add a
Pluggy hook implementation for hexdoc_load_tagged_unions() -> list[Package]
.
Subclasses MUST NOT be generic unless they provide a default value for all
__init_subclass__
arguments. See pydantic/7171 for more info.
Args: key: The dict key for the internal tag. If None, the parent's value is used. value: The expected tag value for this class. Should be None for types which shouldn't be instantiated (eg. abstract classes).
318class TypeTaggedUnion(InternallyTaggedUnion, key="type", value=None): 319 _type: ClassVar[ResourceLocation | NoValueType | None] = None 320 321 def __init_subclass__( 322 cls, 323 *, 324 type: TagValue | InheritType | None = Inherit, 325 **kwargs: Unpack[ConfigDict], 326 ): 327 super().__init_subclass__(value=type, **kwargs) 328 329 match cls._tag_value: 330 case str(raw_value): 331 cls._type = ResourceLocation.from_str(raw_value) 332 case value: 333 cls._type = value 334 335 @classproperty 336 @classmethod 337 def type(cls): 338 return cls._type 339 340 @classproperty 341 @classmethod 342 @override 343 def _tag_value_type(cls) -> type[Any]: 344 return ResourceLocation
Implements internally tagged unions using the Registry pattern.
To ensure your subtypes are loaded even if they're not imported by any file, add a
Pluggy hook implementation for hexdoc_load_tagged_unions() -> list[Package]
.
Subclasses MUST NOT be generic unless they provide a default value for all
__init_subclass__
arguments. See pydantic/7171 for more info.
Args: key: The dict key for the internal tag. If None, the parent's value is used. value: The expected tag value for this class. Should be None for types which shouldn't be instantiated (eg. abstract classes).
Base class for all Pydantic models in hexdoc.
Sets the default model config, and overrides __init__ to allow using the
init_context
context manager to set validation context for constructors.