apytizer 0.0.1a0__py3-none-any.whl → 0.0.1b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. apytizer/__init__.py +2 -12
  2. apytizer/adapters/__init__.py +2 -3
  3. apytizer/adapters/transport_adapter.py +91 -0
  4. apytizer/apis/__init__.py +6 -0
  5. apytizer/apis/abstract_api.py +36 -0
  6. apytizer/apis/web_api.py +461 -0
  7. apytizer/connections/__init__.py +6 -0
  8. apytizer/connections/abstract_connection.py +28 -0
  9. apytizer/connections/http_connection.py +431 -0
  10. apytizer/decorators/__init__.py +5 -5
  11. apytizer/decorators/caching.py +60 -9
  12. apytizer/decorators/chunking.py +105 -0
  13. apytizer/decorators/connection.py +55 -20
  14. apytizer/decorators/json_response.py +93 -0
  15. apytizer/decorators/pagination.py +50 -32
  16. apytizer/endpoints/__init__.py +6 -0
  17. apytizer/endpoints/abstract_endpoint.py +38 -0
  18. apytizer/endpoints/web_endpoint.py +519 -0
  19. apytizer/engines/__init__.py +6 -0
  20. apytizer/engines/abstract_engine.py +45 -0
  21. apytizer/engines/http_engine.py +129 -0
  22. apytizer/errors.py +34 -0
  23. apytizer/factories/__init__.py +5 -0
  24. apytizer/factories/abstract_factory.py +17 -0
  25. apytizer/http_methods.py +34 -0
  26. apytizer/managers/__init__.py +12 -0
  27. apytizer/managers/abstract_manager.py +80 -0
  28. apytizer/managers/base_manager.py +116 -0
  29. apytizer/mappers/__init__.py +6 -0
  30. apytizer/mappers/abstract_mapper.py +48 -0
  31. apytizer/mappers/base_mapper.py +78 -0
  32. apytizer/media_types.py +118 -0
  33. apytizer/models/__init__.py +6 -0
  34. apytizer/models/abstract_model.py +119 -0
  35. apytizer/models/base_model.py +85 -0
  36. apytizer/protocols.py +38 -0
  37. apytizer/repositories/__init__.py +6 -0
  38. apytizer/repositories/abstract_repository.py +81 -0
  39. apytizer/repositories/managed_repository.py +92 -0
  40. apytizer/routes/__init__.py +6 -0
  41. apytizer/routes/abstract_route.py +32 -0
  42. apytizer/routes/base_route.py +138 -0
  43. apytizer/sessions/__init__.py +33 -0
  44. apytizer/sessions/abstract_session.py +63 -0
  45. apytizer/sessions/requests_session.py +125 -0
  46. apytizer/states/__init__.py +6 -0
  47. apytizer/states/abstract_state.py +71 -0
  48. apytizer/states/local_state.py +99 -0
  49. apytizer/utils/__init__.py +9 -4
  50. apytizer/utils/caching.py +39 -0
  51. apytizer/utils/dictionaries.py +375 -0
  52. apytizer/utils/errors.py +104 -0
  53. apytizer/utils/iterables.py +91 -0
  54. apytizer/utils/objects.py +145 -0
  55. apytizer/utils/strings.py +69 -0
  56. apytizer/utils/typing.py +29 -0
  57. apytizer-0.0.1b1.dist-info/METADATA +41 -0
  58. apytizer-0.0.1b1.dist-info/RECORD +60 -0
  59. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b1.dist-info}/WHEEL +1 -2
  60. apytizer/abstracts/__init__.py +0 -8
  61. apytizer/abstracts/api.py +0 -147
  62. apytizer/abstracts/endpoint.py +0 -177
  63. apytizer/abstracts/model.py +0 -50
  64. apytizer/abstracts/session.py +0 -39
  65. apytizer/adapters/transport.py +0 -40
  66. apytizer/base/__init__.py +0 -8
  67. apytizer/base/api.py +0 -510
  68. apytizer/base/endpoint.py +0 -443
  69. apytizer/base/model.py +0 -119
  70. apytizer/decorators/json.py +0 -35
  71. apytizer/utils/generate_key.py +0 -18
  72. apytizer/utils/merge.py +0 -19
  73. apytizer-0.0.1a0.dist-info/METADATA +0 -27
  74. apytizer-0.0.1a0.dist-info/RECORD +0 -25
  75. apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
  76. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b1.dist-info/licenses}/LICENSE +0 -0
apytizer/errors.py ADDED
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/errors.py
3
+
4
+
5
+ class ConnectionError(Exception):
6
+ """Base class for connection errors."""
7
+
8
+
9
+ class ConnectionNotStarted(ConnectionError):
10
+ """Error raised when connection not started."""
11
+
12
+
13
+ class EndpointError(Exception):
14
+ """Base class for endpoint errors."""
15
+
16
+
17
+ class EndpointNotFound(Exception):
18
+ """Error raised when endpoint not found."""
19
+
20
+
21
+ class RequestError(Exception):
22
+ """Base class for request errors."""
23
+
24
+
25
+ class MethodNotAllowed(RequestError):
26
+ """Error raised when request method not allowed."""
27
+
28
+
29
+ class SessionError(Exception):
30
+ """Base class for session errors."""
31
+
32
+
33
+ class SessionNotStarted(SessionError):
34
+ """Error raised when session not started."""
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/factories/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_factory import *
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/factories/abstract_factory.py
3
+ """Abstract factory class interface.
4
+
5
+ This module defines an abstract factory class which provides an interface for
6
+ subclasses to implement.
7
+
8
+ """
9
+
10
+ # Standard Library Imports
11
+ import abc
12
+
13
+ __all__ = ["AbstractFactory"]
14
+
15
+
16
+ class AbstractFactory(abc.ABC):
17
+ """Represents an abstract factory."""
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/http_methods.py
3
+
4
+ # Standard Library Imports
5
+ import enum
6
+ from typing import Optional
7
+
8
+
9
+ @enum.unique
10
+ class HTTPMethod(str, enum.Enum):
11
+ """Implements standard HTTP methods."""
12
+
13
+ @classmethod
14
+ def _missing_(cls, value: object) -> Optional[enum.Enum]:
15
+ result = (
16
+ HTTPMethod(value.upper())
17
+ if (
18
+ isinstance(value, str)
19
+ and not value.isupper()
20
+ and value.upper() in HTTPMethod
21
+ )
22
+ else None
23
+ )
24
+ return result
25
+
26
+ CONNECT = "CONNECT"
27
+ HEAD = "HEAD"
28
+ GET = "GET"
29
+ POST = "POST"
30
+ PUT = "PUT"
31
+ PATCH = "PATCH"
32
+ DELETE = "DELETE"
33
+ OPTIONS = "OPTIONS"
34
+ TRACE = "TRACE"
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/managers/__init__.py
3
+ """Managers.
4
+
5
+ Managers are abstractions around endpoints. Managers act as a bridge between
6
+ their respective endpoints and the repositories and models that rely on them.
7
+
8
+ """
9
+
10
+ # Local Imports
11
+ from .abstract_manager import *
12
+ from .base_manager import *
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/managers/abstract_manager.py
3
+ """Abstract manager class interface.
4
+
5
+ This module defines an abstract manager class which provides an interface for
6
+ subclasses to implement. Each of the abstract methods represents a standard
7
+ create, read, update or delete (CRUD) operation.
8
+
9
+ """
10
+
11
+ # Standard Library Imports
12
+ import abc
13
+ from typing import Any
14
+ from typing import TYPE_CHECKING
15
+
16
+ # Local Imports
17
+ if TYPE_CHECKING:
18
+ from ..models import AbstractModel
19
+
20
+ __all__ = ["AbstractManager"]
21
+
22
+
23
+ class AbstractManager(abc.ABC):
24
+ """Represents an abstract model manager."""
25
+
26
+ @abc.abstractmethod
27
+ def create(self, obj: AbstractModel, /) -> None:
28
+ """Abstract method to create an object.
29
+
30
+ This method must call the `post` method on an associated endpoint to
31
+ create the object.
32
+
33
+ Args:
34
+ obj: Instance of an abstract model subclass.
35
+
36
+ """
37
+ raise NotImplementedError
38
+
39
+ @abc.abstractmethod
40
+ def read(self, ref: Any, /) -> AbstractModel:
41
+ """Abstract method to 'read' an object.
42
+
43
+ This method must call the `get` method on an associated endpoint to
44
+ retrieve object data. The response is used to instantiate an instance
45
+ of the managed object class.
46
+
47
+ Args:
48
+ ref: Reference to object on endpoint.
49
+
50
+ Returns:
51
+ Instance of an abstract model subclass.
52
+
53
+ """
54
+ raise NotImplementedError
55
+
56
+ @abc.abstractmethod
57
+ def update(self, obj: AbstractModel, /) -> None:
58
+ """Abstract method to update an object.
59
+
60
+ This method must call the `put` method on an associated endpoint to
61
+ update object data.
62
+
63
+ Args:
64
+ obj: Instance of an abstract model subclass.
65
+
66
+ """
67
+ raise NotImplementedError
68
+
69
+ @abc.abstractmethod
70
+ def delete(self, obj: AbstractModel, /) -> None:
71
+ """Abstract method to delete an object.
72
+
73
+ This method must call the `delete` method on an associated endpoint
74
+ to delete the object.
75
+
76
+ Args:
77
+ obj: Instance of an abstract model subclass.
78
+
79
+ """
80
+ raise NotImplementedError
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/managers/base_manager.py
3
+ """Base manager class.
4
+
5
+ This module defines a base implementation of a manager class.
6
+
7
+ """
8
+
9
+ # Standard Library Imports
10
+ from __future__ import annotations
11
+ import logging
12
+ from typing import Any
13
+ from typing import Type
14
+ from typing import TYPE_CHECKING
15
+
16
+ # Local Imports
17
+ from .abstract_manager import AbstractManager
18
+ from ..apis import AbstractAPI
19
+ from ..mappers import AbstractMapper
20
+
21
+ if TYPE_CHECKING:
22
+ from ..models import AbstractModel
23
+
24
+ __all__ = ["BaseManager"]
25
+
26
+
27
+ # Initialize logger.
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ class BaseManager(AbstractManager):
32
+ """Base class from which all manager implementations are derived.
33
+
34
+ Args:
35
+ __api: API instance.
36
+
37
+ Raises:
38
+ TypeError: when argument is not type 'Endpoint'.
39
+
40
+ """
41
+
42
+ def __new__(
43
+ cls: Type[BaseManager],
44
+ __api: AbstractAPI,
45
+ __mapper: AbstractMapper,
46
+ /,
47
+ ) -> BaseManager:
48
+ if not isinstance(__api, AbstractAPI): # type: ignore
49
+ message = f"expected type 'WebAPI', got {type(__api)} instead"
50
+ raise TypeError(message)
51
+
52
+ if not isinstance(__mapper, AbstractMapper): # type: ignore
53
+ message = f"expected type 'Mapper', got {type(__mapper)} instead"
54
+ raise TypeError(message)
55
+
56
+ instance = super().__new__(cls) # type: ignore
57
+ return instance
58
+
59
+ def __init__(
60
+ self,
61
+ __api: AbstractAPI,
62
+ __mapper: AbstractMapper,
63
+ /,
64
+ ) -> None:
65
+ self._api = __api
66
+ self._mapper = __mapper
67
+
68
+ def __repr__(self) -> str:
69
+ result = "<{cls!s}>".format(cls=self.__class__.__name__)
70
+ return result
71
+
72
+ def create(self, obj: "AbstractModel", /) -> None:
73
+ """Create an object.
74
+
75
+ Args:
76
+ obj: Object to create.
77
+
78
+ """
79
+ raise NotImplementedError
80
+
81
+ def read(self, ref: Any, /) -> "AbstractModel":
82
+ """Read an object.
83
+
84
+ Calls `get` method on associated endpoint to retrieve object data.
85
+ Uses response to instantiate an instance of the managed object class.
86
+
87
+ Args:
88
+ ref: Reference to object on endpoint.
89
+
90
+ Returns:
91
+ Object instance.
92
+
93
+ """
94
+ raise NotImplementedError
95
+
96
+ def update(self, obj: "AbstractModel", /) -> None:
97
+ """Update an object.
98
+
99
+ Calls `put` method on associated endpoint to update object data.
100
+
101
+ Args:
102
+ obj: Object to update.
103
+
104
+ """
105
+ raise NotImplementedError
106
+
107
+ def delete(self, obj: "AbstractModel", /) -> None:
108
+ """Delete an object.
109
+
110
+ Calls `delete` method on associated endpoint to delete the object.
111
+
112
+ Args:
113
+ obj: Object to delete.
114
+
115
+ """
116
+ raise NotImplementedError
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/mappers/__init__.py
3
+ """Mappers."""
4
+
5
+ # Local Imports
6
+ from .abstract_mapper import *
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/mappers/abstract_mapper.py
3
+ """Abstract mapper class interface.
4
+
5
+ This module defines an abstract mapper class which provides an interface for
6
+ subclasses to implement.
7
+
8
+ """
9
+
10
+ # Standard Library Imports
11
+ import abc
12
+ from typing import Mapping
13
+ from typing import Type
14
+ from typing import TYPE_CHECKING
15
+
16
+ # Local Imports
17
+ if TYPE_CHECKING:
18
+ from ..models import AbstractModel
19
+ from ..routes import AbstractRoute
20
+
21
+ __all__ = ["AbstractMapper"]
22
+
23
+
24
+ class AbstractMapper(abc.ABC):
25
+ """Represents an abstract mapper.
26
+
27
+ Each mapper is an association between a Python class and an API endpoint,
28
+ which allows ORM operations against the class.
29
+
30
+ """
31
+
32
+ @property
33
+ @abc.abstractmethod
34
+ def model(self) -> Type["AbstractModel"]:
35
+ """Model class."""
36
+ raise NotImplementedError
37
+
38
+ @property
39
+ @abc.abstractmethod
40
+ def route(self) -> "AbstractRoute":
41
+ """Routes."""
42
+ raise NotImplementedError
43
+
44
+ @property
45
+ @abc.abstractmethod
46
+ def properties(self) -> Mapping[str, str]:
47
+ """Properties."""
48
+ raise NotImplementedError
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/mappers/base_mapper.py
3
+ """Base Mapper Class.
4
+
5
+ This module defines the base mapper class implementation.
6
+
7
+ """
8
+
9
+ # Standard Library Imports
10
+ from __future__ import annotations
11
+ from types import MappingProxyType
12
+ from typing import Mapping
13
+ from typing import Type
14
+
15
+ # Local Imports
16
+ from .abstract_mapper import AbstractMapper
17
+ from ..models import AbstractModel
18
+ from ..routes import AbstractRoute
19
+
20
+ __all__ = ["Mapper"]
21
+
22
+
23
+ class Mapper(AbstractMapper):
24
+ """Implements a mapper.
25
+
26
+ Each mapper is an association between a Python class and an API endpoint,
27
+ which allows ORM operations against the class.
28
+
29
+ """
30
+
31
+ def __new__(
32
+ cls: Type[Mapper],
33
+ __class: Type[AbstractModel],
34
+ __route: AbstractRoute,
35
+ __properties: Mapping[str, str],
36
+ /,
37
+ ) -> Mapper:
38
+ if not issubclass(__class, AbstractModel): # type: ignore
39
+ message = f"{__class} is not a subclass of 'AbstractModel'"
40
+ raise TypeError(message)
41
+
42
+ if not isinstance(__route, AbstractRoute): # type: ignore
43
+ message = f"expected type 'Route', got {type(__route)} instead"
44
+ raise TypeError(message)
45
+
46
+ if not isinstance(__properties, Mapping): # type: ignore
47
+ expected = "expected type 'Mapping'"
48
+ actual = f"got {type(__properties)} instead"
49
+ message = ", ".join([expected, actual])
50
+ raise TypeError(message)
51
+
52
+ instance = super().__new__(cls) # type: ignore
53
+ return instance
54
+
55
+ def __init__(
56
+ self,
57
+ __class: Type[AbstractModel],
58
+ __route: AbstractRoute,
59
+ __properties: Mapping[str, str],
60
+ ) -> None:
61
+ self._class = __class
62
+ self._route = __route
63
+ self._properties = MappingProxyType(__properties)
64
+
65
+ @property
66
+ def model(self) -> Type[AbstractModel]:
67
+ """Model."""
68
+ raise NotImplementedError
69
+
70
+ @property
71
+ def route(self) -> AbstractRoute:
72
+ """Route."""
73
+ raise NotImplementedError
74
+
75
+ @property
76
+ def properties(self) -> Mapping[str, str]:
77
+ """Properties."""
78
+ raise NotImplementedError
@@ -0,0 +1,118 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/media_types.py
3
+ """Media types (previously MIME types) and subtypes.
4
+
5
+ .. _IANA:
6
+ https://www.iana.org/assignments/media-types/media-types.xhtml
7
+
8
+ """
9
+
10
+ # Standard Library Imports
11
+ from __future__ import annotations
12
+ import enum
13
+ import mimetypes
14
+ import re
15
+ from typing import Optional
16
+
17
+
18
+ @enum.unique
19
+ class MediaType(str, enum.Enum):
20
+ """Implements media types."""
21
+
22
+ @property
23
+ def type(self) -> Optional[str]:
24
+ """Content type."""
25
+ match = re.match(r"[a-z]+(?=/)", self._value_)
26
+ result = match.group(0) if match else None
27
+ return result
28
+
29
+ @property
30
+ def subtype(self) -> Optional[str]:
31
+ """Content subtype."""
32
+ match = re.search(r"(?<=/)[a-z+-]+", self._value_)
33
+ result = match.group(0) if match else None
34
+ return result
35
+
36
+ @property
37
+ def extension(self) -> Optional[str]:
38
+ """File extension."""
39
+ result = mimetypes.guess_extension(self._value_)
40
+ return result
41
+
42
+ # application
43
+ APPLICATION_JAVASCRIPT = "application/javascript"
44
+ APPLICATION_JSON = "application/json"
45
+ APPLICATION_MANIFEST = "application/manifest+json"
46
+ APPLICATION_MSWORD = "application/msword"
47
+ APPLICATION_OCTET_STREAM = "application/octet-stream"
48
+ APPLICATION_ODA = "application/oda"
49
+ APPLICATION_PDF = "application/pdf"
50
+ APPLICATION_PKCS7_MIME = "application/pkcs7-mime"
51
+ APPLICATION_POSTSCRIPT = "application/postscript"
52
+ APPLICATION_RTF = "application/rtf"
53
+ APPLICATION_RTX = "application/rtx"
54
+ APPLICATION_SQL = "application/sql"
55
+ APPLICATION_TAR = "application/x-tar"
56
+ APPLICATION_XML = "application/xml"
57
+ APPLICATION_ZIP = "application/zip"
58
+
59
+ # audio
60
+ AUDIO_AIFF = "audio/x-aiff"
61
+ AUDIO_BASIC = "audio/basic"
62
+ AUDIO_MP4 = "audio/mp4"
63
+ AUDIO_MPEG = "audio/mpeg"
64
+ AUDIO_MIDI = "audio/midi"
65
+ AUDIO_REALAUDIO = "audio/x-pn-realaudio"
66
+ AUDIO_WAV = "audio/x-wav"
67
+
68
+ # image
69
+ IMAGE_BMP = "image/bmp"
70
+ IMAGE_GIF = "image/gif"
71
+ IMAGE_ICON = "image/vnd.microsoft.icon"
72
+ IMAGE_IEF = "image/ief"
73
+ IMAGE_JPEG = "image/jpeg"
74
+ IMAGE_PICT = "image/pict"
75
+ IMAGE_PNG = "image/png"
76
+ IMAGE_RGB = "image/x-rgb"
77
+ IMAGE_SVG = "image/svg+xml"
78
+ IMAGE_TIFF = "image/tiff"
79
+
80
+ # message
81
+ MESSAGE_PARTIAL = "message/partial"
82
+ MESSAGE_RFC822 = "message/rfc822"
83
+
84
+ # multipart
85
+ MULTIPART_BYTERANGES = "multipart/byteranges"
86
+ MULTIPART_ENCRYPTED = "multipart/encrypted"
87
+ MULTIPART_FORM_DATA = "multipart/form-data"
88
+ MULTIPART_HEADER_SET = "multipart/header-set"
89
+ MULTIPART_MULTILINGUAL = "multipart/multilingual"
90
+ MULTIPART_SIGNED = "multipart/signed"
91
+
92
+ # text
93
+ TEXT_CSS = "text/css"
94
+ TEXT_CSV = "text/csv"
95
+ TEXT_DNS = "text/dns"
96
+ TEXT_HTML = "text/html"
97
+ TEXT_JAVASCRIPT = "text/javascript"
98
+ TEXT_MARKDOWN = "text/markdown"
99
+ TEXT_PLAIN = "text/plain"
100
+ TEXT_PYTHON = "text/x-python"
101
+ TEXT_RTF = "text/rtf"
102
+ TEXT_RTX = "text/rtx"
103
+ TEXT_SETEXT = "text/x-setext"
104
+ TEXT_SGML = "text/x-sgml"
105
+ TEXT_STRINGS = "text/strings"
106
+ TEXT_TSV = "text/tab-separated-values"
107
+ TEXT_VCARD = "text/x-vcard"
108
+ TEXT_XML = "text/xml"
109
+
110
+ # video
111
+ VIDEO_H264 = "video/H264"
112
+ VIDEO_MP4 = "video/mp4"
113
+ VIDEO_MPEG = "video/mpeg"
114
+ VIDEO_MSVIDEO = "video/x-msvideo"
115
+ VIDEO_QUICKTIME = "video/quicktime"
116
+ VIDEO_RAW = "video/raw"
117
+ VIDEO_SGI_MOVIE = "video/x-sgi-movie"
118
+ VIDEO_WEBM = "video/webm"
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/models/__init__.py
3
+
4
+ # Local Imports
5
+ from .abstract_model import *
6
+ from .base_model import *
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+ # src/apytizer/models/abstract_model.py
3
+ """Abstract model class interface.
4
+
5
+ This module defines an abstract model class which provides an interface
6
+ for subclasses to implement.
7
+
8
+ """
9
+
10
+ # Standard Library Imports
11
+ from __future__ import annotations
12
+ import abc
13
+ from typing import Any
14
+ from typing import Mapping
15
+ from typing import TYPE_CHECKING
16
+
17
+ # Local Imports
18
+ if TYPE_CHECKING:
19
+ from ..managers import AbstractManager
20
+ from ..states import AbstractState
21
+
22
+ __all__ = ["AbstractModel"]
23
+
24
+
25
+ class AbstractModel(abc.ABC):
26
+ """Represents an abstract model."""
27
+
28
+ @property
29
+ def manager(self) -> AbstractManager:
30
+ """Manager for model."""
31
+ return getattr(self, "__manager__")
32
+
33
+ @manager.setter
34
+ def manager(self, manager: AbstractManager, /) -> None:
35
+ setattr(self, "__manager__", manager)
36
+
37
+ @manager.deleter
38
+ def manager(self) -> None:
39
+ delattr(self, "__manager__")
40
+
41
+ @abc.abstractmethod
42
+ def __eq__(self, other: object) -> bool:
43
+ """Abstract method for determining whether model is equal to another.
44
+
45
+ Returns:
46
+ Whether models are equal.
47
+
48
+ """
49
+ raise NotImplementedError
50
+
51
+ @abc.abstractmethod
52
+ def __hash__(self) -> int:
53
+ """Abstract method for returning the hash value of a model.
54
+
55
+ Returns:
56
+ Hash value.
57
+
58
+ """
59
+ raise NotImplementedError
60
+
61
+ @abc.abstractmethod
62
+ def __repr__(self) -> str:
63
+ """Abstract method for returning string representation of model."""
64
+ raise NotImplementedError
65
+
66
+
67
+ class AbstractStatefulModel(AbstractModel):
68
+ """Class represents an abstract stateful model."""
69
+
70
+ @property
71
+ @abc.abstractmethod
72
+ def state(self) -> AbstractState:
73
+ """State."""
74
+ raise NotImplementedError
75
+
76
+ @abc.abstractmethod
77
+ def __contains__(self, other: object) -> bool:
78
+ """Abstract method for determining whether object in state.
79
+
80
+ Returns:
81
+ Whether object in state.
82
+
83
+ """
84
+ raise NotImplementedError
85
+
86
+ @abc.abstractmethod
87
+ def __getattr__(self, name: str) -> Any:
88
+ """Abstract method for getting an attribute from state.
89
+
90
+ Returns:
91
+ Value of attribute.
92
+
93
+ """
94
+ raise NotImplementedError
95
+
96
+ @abc.abstractmethod
97
+ def __getitem__(self, name: str) -> Any:
98
+ """Abstract method for getting an item from state.
99
+
100
+ Returns:
101
+ Value of item.
102
+
103
+ """
104
+ raise NotImplementedError
105
+
106
+ @abc.abstractmethod
107
+ def update(self, __m: Mapping[str, Any], **kwargs: Any) -> None:
108
+ """Abstract method for updating state of model."""
109
+ raise NotImplementedError
110
+
111
+ @abc.abstractmethod
112
+ def rollback(self) -> None:
113
+ """Abstract method for rolling back changes to state of model."""
114
+ raise NotImplementedError
115
+
116
+ @abc.abstractmethod
117
+ def save(self) -> None:
118
+ """Abstract method for saving changes to state of model."""
119
+ raise NotImplementedError