mirror of
https://github.com/wassname/pytorch-ts.git
synced 2026-07-13 17:45:02 +08:00
4fa5a991bd
for issue #11
172 lines
5.8 KiB
Python
172 lines
5.8 KiB
Python
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
# You may not use this file except in compliance with the License.
|
|
# A copy of the License is located at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# or in the "license" file accompanying this file. This file is distributed
|
|
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
# express or implied. See the License for the specific language governing
|
|
# permissions and limitations under the License.
|
|
|
|
|
|
import functools
|
|
import inspect
|
|
from collections import OrderedDict
|
|
from typing import Any
|
|
|
|
import torch
|
|
from pydantic import BaseConfig, BaseModel, create_model
|
|
|
|
from pts.core.serde import dump_code
|
|
|
|
|
|
class BaseValidatedInitializerModel(BaseModel):
|
|
"""
|
|
Base Pydantic model for components with :func:`validated` initializers.
|
|
|
|
See Also
|
|
--------
|
|
validated
|
|
Decorates an initializer methods with argument validation logic.
|
|
"""
|
|
|
|
class Config(BaseConfig):
|
|
"""
|
|
`Config <https://pydantic-docs.helpmanual.io/#model-config>`_ for the
|
|
Pydantic model inherited by all :func:`validated` initializers.
|
|
|
|
Allows the use of arbitrary type annotations in initializer parameters.
|
|
"""
|
|
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
def validated(base_model=None):
|
|
"""
|
|
Decorates an ``__init__`` method with typed parameters with validation
|
|
and auto-conversion logic.
|
|
|
|
>>> class ComplexNumber:
|
|
... @validated()
|
|
... def __init__(self, x: float = 0.0, y: float = 0.0) -> None:
|
|
... self.x = x
|
|
... self.y = y
|
|
|
|
Classes with decorated initializers can be instantiated using arguments of
|
|
another type (e.g. an ``y`` argument of type ``str`` ). The decorator
|
|
handles the type conversion logic.
|
|
|
|
>>> c = ComplexNumber(y='42')
|
|
>>> (c.x, c.y)
|
|
(0.0, 42.0)
|
|
|
|
If the bound argument cannot be converted, the decorator throws an error.
|
|
|
|
>>> c = ComplexNumber(y=None)
|
|
Traceback (most recent call last):
|
|
...
|
|
pydantic.error_wrappers.ValidationError: 1 validation error for ComplexNumberModel
|
|
y
|
|
none is not an allowed value (type=type_error.none.not_allowed)
|
|
|
|
Internally, the decorator delegates all validation and conversion logic to
|
|
`a Pydantic model <https://pydantic-docs.helpmanual.io/>`_, which can be
|
|
accessed through the ``Model`` attribute of the decorated initiazlier.
|
|
|
|
>>> ComplexNumber.__init__.Model
|
|
<class 'ComplexNumberModel'>
|
|
|
|
The Pydantic model is synthesized automatically from on the parameter
|
|
names and types of the decorated initializer. In the ``ComplexNumber``
|
|
example, the synthesized Pydantic model corresponds to the following
|
|
definition.
|
|
|
|
>>> class ComplexNumberModel(BaseValidatedInitializerModel):
|
|
... x: float = 0.0
|
|
... y: float = 0.0
|
|
|
|
|
|
Clients can optionally customize the base class of the synthesized
|
|
Pydantic model using the ``base_model`` decorator parameter. The default
|
|
behavior uses :class:`BaseValidatedInitializerModel` and its
|
|
`model config <https://pydantic-docs.helpmanual.io/#config>`_.
|
|
|
|
See Also
|
|
--------
|
|
BaseValidatedInitializerModel
|
|
Default base class for all synthesized Pydantic models.
|
|
"""
|
|
|
|
def validator(init):
|
|
init_qualname = dict(inspect.getmembers(init))["__qualname__"]
|
|
init_clsnme = init_qualname.split(".")[0]
|
|
init_params = inspect.signature(init).parameters
|
|
init_fields = {
|
|
param.name: (
|
|
param.annotation
|
|
if param.annotation != inspect.Parameter.empty
|
|
else Any,
|
|
param.default if param.default != inspect.Parameter.empty else ...,
|
|
)
|
|
for param in init_params.values()
|
|
if param.name != "self"
|
|
and param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
|
}
|
|
|
|
if base_model is None:
|
|
PydanticModel = create_model(
|
|
f"{init_clsnme}Model",
|
|
__config__=BaseValidatedInitializerModel.Config,
|
|
**init_fields,
|
|
)
|
|
else:
|
|
PydanticModel = create_model(
|
|
f"{init_clsnme}Model", __base__=base_model, **init_fields,
|
|
)
|
|
|
|
def validated_repr(self) -> str:
|
|
return dump_code(self)
|
|
|
|
def validated_getnewargs_ex(self):
|
|
return (), self.__init_args__
|
|
|
|
@functools.wraps(init)
|
|
def init_wrapper(*args, **kwargs):
|
|
self, *args = args
|
|
|
|
nmargs = {
|
|
name: arg
|
|
for (name, param), arg in zip(list(init_params.items()), [self] + args)
|
|
if name != "self"
|
|
}
|
|
model = PydanticModel(**{**nmargs, **kwargs})
|
|
|
|
# merge nmargs, kwargs, and the model fields into a single dict
|
|
all_args = {**nmargs, **kwargs, **model.__dict__}
|
|
|
|
# save the merged dictionary for Representable use, but only of the
|
|
# __init_args__ is not already set in order to avoid overriding a
|
|
# value set by a subclass initializer in super().__init__ calls
|
|
if not getattr(self, "__init_args__", {}):
|
|
self.__init_args__ = OrderedDict(
|
|
{
|
|
name: arg
|
|
for name, arg in sorted(all_args.items())
|
|
if type(arg) != torch.nn.ParameterDict
|
|
}
|
|
)
|
|
self.__class__.__getnewargs_ex__ = validated_getnewargs_ex
|
|
self.__class__.__repr__ = validated_repr
|
|
|
|
return init(self, **all_args)
|
|
|
|
# attach the Pydantic model as the attribute of the initializer wrapper
|
|
setattr(init_wrapper, "Model", PydanticModel)
|
|
|
|
return init_wrapper
|
|
|
|
return validator
|