Module xmodel.remote.model
Expand source code
from typing import TypeVar, Generic, TYPE_CHECKING
from xmodel.base.model import BaseModel, Self
from xsentinels.default import Default
if TYPE_CHECKING:
from xmodel.remote.api import RemoteApi
# Can't forward-ref the bound type here (ie: 'RemoteModel'), so put BaseModel in at least...
M = TypeVar("M", bound=BaseModel)
def _lazy_load_types(cls):
"""
Lazy import BaseApi into module, helps resolve BaseApi forward-refs;
ie: `api: "RemoteApi[T]"`
We need to resolve these forward-refs due to use of `get_type_hints()` in
BaseModel.__init_subclass__; so get_type_hints can find the correct type for the
forward-ref string in out `RemoteModel` class below.
Sets it in such a way so IDE's such as pycharm don't get confused + pydoc3
can still find it and use the type forward-reference.
See `xmodel.base.model.BaseModel.__init_subclass__` for more details.
"""
if 'BaseApi' not in globals():
from xmodel.remote.api import RemoteApi
globals()['RemoteApi'] = RemoteApi
class RemoteModel(BaseModel, lazy_loader=_lazy_load_types):
api: 'RemoteApi[Self]' = None
# todo: I think we will want to make the type on the `id` field a TypeVar of some sort,
# sometimes we will want to use a `str` for it's type [etc].
id: int = None
""" Primary identifier for object, used with API endpoint. """
def __init__(self, *args, id=Default, **initial_values):
super().__init__(*args, **initial_values)
if id is not Default:
self.id = id
def __repr__(self):
message = super().__repr__().split("(")[1][:-1]
response_state = self.api.response_state
response_state_attrs = []
if response_state.had_error is not None:
response_state_attrs.append('had_error')
if response_state.response_code is not None and response_state.response_code != 200:
response_state_attrs.append('response_code')
if response_state.errors is not None:
response_state_attrs.append('errors')
for attr in response_state_attrs:
message += f', __{attr}={getattr(response_state, attr, None)}'
return f"{self.__class__.__name__}({message})"
Classes
class RemoteModel (*args, id=Default, **initial_values)
-
Used as the abstract base-class for classes/object that communicate with our REST API.
This is one of the main classes, and it's highly recommend you read the SDK Library Overview first, if you have not already. That document has many basic examples of using this class along with other related classes.
Attributes that start with
_
or don't have a type-hint are not considered fields on the object that automatically get mapped to/from the JSON that is passed in. For more details see Type Hints.When you sub-class
BaseModel
, you can create your own Model class, with your own fields/attrs. You can pass class arguments/paramters in when you declare your sub-class. The Model-subclass can provide parameters to the super class during class construction.In the example below, notice the
base_url
part. That's a class argument, that is used by the super-class during the construction of the sub-class (before any instances are created). In this case it takes this and stores it onxmodel.rest.RestStructure.base_model_url
as part of the structure information for theBaseModel
subclass.See Basic Model Example for an example of what class arguments are or look at this example below using a RestModel:
>>> # 'base_url' part is a class argument: >>> from xmodel.rest import RestModel >>> class Account(RestModel["Account"], base_url='/account'): >>> id: str >>> name: str
These class arguments are sent to a special method
BaseStructure.configure_for_model_type()
. See that methods docs for a list of avaliable class-arguments.See
BaseModel.__init_subclass__
for more on the internal details of how this works exactly.Note: In the case of
base_url
example above, it's the base-url-endpoint for the model.If you want to know more about that see
xmodel.rest.RestClient.url_for_endpoint
. It has details on how the final requestUrl
is constructed.This class also allows you to more easily with with JSON data via:
BaseApi.json()
BaseApi.update_from_json()
- Or passing a JSON dict as the first arrument to
BaseModel.__init__
.
Other important related classes are listed below.
BaseApi
Accessable viaBaseModel.api
.xmodel.rest.RestClient
: Accessable viaxmodel.base.api.BaseApi.client
.xmodel.rest.settings.RestSettings
: Accessable viaxmodel.base.api.BaseApi.settings
.BaseStructure
: Accessable viaBaseApi.structure
xmodel.base.auth.BaseAuth
: Accessable viaxmodel.base.api.BaseApi.auth
Tip: For all of the above, you can change what class is allocated for each one
by changing the type-hint on a subclass.
Creates a new model object. The first/second params need to be passed as positional arguments. The rest must be sent as key-word arguments. Everything is optional.
Args
id
- Specify the
BaseModel.id
attribute, if you know it. If left as Default, nothing will be set on it. It could be set to something via args[0] (ie: a JSON dict). If you do provide a value, it be set last after everything else has been set. *args
-
I don't want to take names from what you could put into 'initial_values', so I keep it as position-only *args. Once Python 3.8 comes out, we can use a new feature where you can specify some arguments as positional-only and not keyword-able.
FirstArg - If Dict:
If raw dictionary parsed from JSON string. It just calls
self.api.update_from_json(args[0])
for you.FirstArt - If BaseModel:
If a
BaseModel
, will copy fields over that have the same name. You can use this to duplicate a Model object, if you want to copy it. Or can be used to copy fields from one model type into another, on fields that are the same name.Will ignore fields that are present on one but not the other. Only copy fields that are on both models types.
**initial_values
- Let's you specify other attribute values for convenience.
They will be set into the object the same way you would normally doing it:
ie:
model_obj.some_attr = v
is the same asModelClass(some_attr=v)
.
Expand source code
class RemoteModel(BaseModel, lazy_loader=_lazy_load_types): api: 'RemoteApi[Self]' = None # todo: I think we will want to make the type on the `id` field a TypeVar of some sort, # sometimes we will want to use a `str` for it's type [etc]. id: int = None """ Primary identifier for object, used with API endpoint. """ def __init__(self, *args, id=Default, **initial_values): super().__init__(*args, **initial_values) if id is not Default: self.id = id def __repr__(self): message = super().__repr__().split("(")[1][:-1] response_state = self.api.response_state response_state_attrs = [] if response_state.had_error is not None: response_state_attrs.append('had_error') if response_state.response_code is not None and response_state.response_code != 200: response_state_attrs.append('response_code') if response_state.errors is not None: response_state_attrs.append('errors') for attr in response_state_attrs: message += f', __{attr}={getattr(response_state, attr, None)}' return f"{self.__class__.__name__}({message})"
Ancestors
- BaseModel
- abc.ABC
Class variables
var api : RemoteApi[BaseModel]
-
Used to access the api class, which is used to retrieve/send objects to/from api …
var id : int
-
Primary identifier for object, used with API endpoint.
Static methods
def __init_subclass__(*, lazy_loader: Callable[[Type[~M]], None] = None, **kwargs)
-
Inherited from:
BaseModel
.__init_subclass__
We take all arguments (except
lazy_loader
) passed into here and send them to the method on our structure: …