our-object-local 0.0.1__tar.gz
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.
- our_object_local-0.0.1/PKG-INFO +19 -0
- our_object_local-0.0.1/README.md +58 -0
- our_object_local-0.0.1/our_object_local/src/__init__.py +0 -0
- our_object_local-0.0.1/our_object_local/src/our_object.py +184 -0
- our_object_local-0.0.1/our_object_local/src/py.typed +0 -0
- our_object_local-0.0.1/our_object_local/src/version.py +1 -0
- our_object_local-0.0.1/our_object_local.egg-info/PKG-INFO +19 -0
- our_object_local-0.0.1/our_object_local.egg-info/SOURCES.txt +11 -0
- our_object_local-0.0.1/our_object_local.egg-info/dependency_links.txt +1 -0
- our_object_local-0.0.1/our_object_local.egg-info/top_level.txt +1 -0
- our_object_local-0.0.1/pyproject.toml +26 -0
- our_object_local-0.0.1/setup.cfg +4 -0
- our_object_local-0.0.1/setup.py +37 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: our-object-local
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: PyPI our-object-local Python Package owned by Circlez.ai
|
|
5
|
+
Home-page: https://github.com/circles-zone/our-object-local-python-package
|
|
6
|
+
Author: Circles
|
|
7
|
+
Author-email: info@circlez.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: author-email
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: summary
|
|
18
|
+
|
|
19
|
+
PyPI our-object-local Python Package owned by Circlez.ai
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# our-object-local
|
|
2
|
+
|
|
3
|
+
`OurObject` - the root of the circlez business-object model.
|
|
4
|
+
|
|
5
|
+
Moved here from `python_sdk_remote/src/our_object.py` so that a package wanting
|
|
6
|
+
the base class no longer has to take the whole SDK. `python-sdk-remote` keeps
|
|
7
|
+
re-exporting `OurObject`, so every existing import goes on working.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from our_object_local.our_object import OurObject
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Contact(OurObject):
|
|
14
|
+
def __init__(self, contact_id: int = None, **kwargs):
|
|
15
|
+
super().__init__(entity_name="contact", id=contact_id, **kwargs)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Why this is its own package
|
|
19
|
+
|
|
20
|
+
`fields-local` depends on `python-sdk-remote`. Putting the `fields-local`-backed
|
|
21
|
+
field helpers on `OurObject` while it lived inside `python-sdk-remote` would
|
|
22
|
+
have made `python-sdk-remote` depend on `fields-local` - a cycle. Splitting the
|
|
23
|
+
base class out breaks it:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
our-object-field-local -> fields-local -> python-sdk-remote -> our-object-local
|
|
27
|
+
| ^
|
|
28
|
+
+--------------------------------------------------------+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**This package therefore declares no circlez dependencies at all.** That is a
|
|
32
|
+
rule, not a coincidence, and `our_object_test.py` asserts it in a fresh
|
|
33
|
+
interpreter so it cannot rot. It is also why `OurObject` no longer carries
|
|
34
|
+
`MiniLogger`: `logger-local` depends on `python-sdk-remote`, which depends on
|
|
35
|
+
this package.
|
|
36
|
+
|
|
37
|
+
It ships `py.typed` (PEP 561), so consumers get the real annotations from mypy
|
|
38
|
+
instead of `Any`.
|
|
39
|
+
|
|
40
|
+
## TODOs
|
|
41
|
+
|
|
42
|
+
- `get_package_version()` returns this package's version, which is as wrong for
|
|
43
|
+
a derived class as returning `python-sdk-remote`'s was. Retire it, or resolve
|
|
44
|
+
the subclass's real distribution?
|
|
45
|
+
- The class-level `fields` set and the two unreferenced `__convert_*` methods
|
|
46
|
+
are replaced by `OurObjectFields` in `our-object-field-local`.
|
|
47
|
+
|
|
48
|
+
## Versions
|
|
49
|
+
|
|
50
|
+
[pub] 0.0.1 Initial version - `OurObject` moved in from `python-sdk-remote`,
|
|
51
|
+
without the logger, with annotations and `py.typed`.
|
|
52
|
+
|
|
53
|
+
## Install dependencies
|
|
54
|
+
|
|
55
|
+
It's advised to use venv.
|
|
56
|
+
|
|
57
|
+
Run `pip install -r requirements-dev.txt` from the bash terminal - there are no
|
|
58
|
+
runtime dependencies to install.
|
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""The root of the business-object model.
|
|
2
|
+
|
|
3
|
+
Moved here from `python_sdk_remote/src/our_object.py` so that anything wanting
|
|
4
|
+
the base class no longer has to take the whole SDK. `python-sdk-remote` keeps
|
|
5
|
+
re-exporting `OurObject`, so every existing import goes on working.
|
|
6
|
+
|
|
7
|
+
This package deliberately declares **no circlez dependencies at all**. That is
|
|
8
|
+
what lets `our-object-field-local` sit above `fields-local` without a cycle:
|
|
9
|
+
|
|
10
|
+
our-object-field-local -> fields-local -> python-sdk-remote -> our-object-local
|
|
11
|
+
|
|
12
|
+
Consequence of the same rule: the `MiniLogger` calls the original carried are
|
|
13
|
+
gone. `logger-local` depends on `python-sdk-remote`, which will depend on this
|
|
14
|
+
package, so importing a logger here would close the loop. Nothing is lost -
|
|
15
|
+
`logger.start`/`logger.end` fired on every single attribute read in `get()`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from abc import ABC
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from typing import Any, Optional
|
|
22
|
+
|
|
23
|
+
from .version import PACKAGE_VERSION
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OurObject(ABC):
|
|
27
|
+
"""Base class for all business objects in the system.
|
|
28
|
+
|
|
29
|
+
Provides JSON serialization/deserialization, equality comparison, string
|
|
30
|
+
representation and attribute access. Extend it for every business entity
|
|
31
|
+
so behaviour stays consistent.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
entity_name: Optional[str] = None
|
|
35
|
+
id: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
# Needed by print_our_object_with_id()
|
|
38
|
+
name: Optional[str] = None
|
|
39
|
+
number: Optional[int] = None
|
|
40
|
+
|
|
41
|
+
# TODO `fields` is a class-level mutable set, so it is shared by every
|
|
42
|
+
# subclass that does not override it, and the print_our_object_with_id()
|
|
43
|
+
# it is commented for exists in no installed package. It is replaced by
|
|
44
|
+
# OurObjectFields in our-object-field-local; kept here unchanged so this
|
|
45
|
+
# move stays a move.
|
|
46
|
+
fields = {
|
|
47
|
+
# Needed by print_our_object_with_id()
|
|
48
|
+
"name",
|
|
49
|
+
"number"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def __init__(self, entity_name: Optional[str] = None,
|
|
53
|
+
id: Optional[int] = None, **kwargs: Any) -> None:
|
|
54
|
+
self.set_entity_name(entity_name)
|
|
55
|
+
self.set_id(id)
|
|
56
|
+
self.kwargs = kwargs
|
|
57
|
+
|
|
58
|
+
# TODO Neither __convert_ method is called anywhere in any installed
|
|
59
|
+
# circlez package, and both are name-mangled, so no subclass can reach
|
|
60
|
+
# them either. Slated for removal with the OurObjectFields work.
|
|
61
|
+
def __convert_kwargs_to_fields(self, kwargs: dict, fields: set) -> None:
|
|
62
|
+
"""Convert kwargs to object fields based on allowed fields list."""
|
|
63
|
+
for field, value in kwargs.items():
|
|
64
|
+
if field in fields:
|
|
65
|
+
setattr(self, field, value)
|
|
66
|
+
|
|
67
|
+
def __convert_fields_to_kwargs(self, fields: set, kwargs: dict) -> None:
|
|
68
|
+
"""Convert fields to kwargs format."""
|
|
69
|
+
for field in fields:
|
|
70
|
+
if field in kwargs:
|
|
71
|
+
setattr(self, field, kwargs[field])
|
|
72
|
+
else:
|
|
73
|
+
setattr(self, field, None)
|
|
74
|
+
|
|
75
|
+
def set_entity_name(self, entity_name: Optional[str]) -> None:
|
|
76
|
+
self.entity_name = entity_name
|
|
77
|
+
|
|
78
|
+
def get_id_column_name(self) -> str:
|
|
79
|
+
"""Returns the name of the column that is used as the id of the object"""
|
|
80
|
+
# BEHAVIOUR NOTE: was `self.entity_name + "_id"`, which raised
|
|
81
|
+
# TypeError on the default entity_name of None. Forced by the
|
|
82
|
+
# annotations this package now ships - see py.typed in the PR body.
|
|
83
|
+
id_column_name = str(self.entity_name) + "_id"
|
|
84
|
+
return id_column_name
|
|
85
|
+
|
|
86
|
+
def set_id(self, id: Optional[int]) -> None:
|
|
87
|
+
self.id = id
|
|
88
|
+
|
|
89
|
+
def get_id(self) -> Optional[int]:
|
|
90
|
+
return self.id
|
|
91
|
+
|
|
92
|
+
# Commented so we can use it in database foreach()
|
|
93
|
+
# @abstractmethod
|
|
94
|
+
def get_name(self) -> Optional[str]:
|
|
95
|
+
"""Returns the name of the object"""
|
|
96
|
+
# raise NotImplementedError(
|
|
97
|
+
# "Subclasses must implement the 'get_name' method.")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def get(self, attr_name: str) -> Any:
|
|
101
|
+
"""Returns the value of the attribute with the given name"""
|
|
102
|
+
# BEHAVIOUR NOTE: the getattr default of None was already there, but
|
|
103
|
+
# .get() was then called on it unconditionally, so any instance built
|
|
104
|
+
# without __init__ - from_json does exactly that - raised
|
|
105
|
+
# AttributeError. Guarding it is forced by the annotations.
|
|
106
|
+
arguments = getattr(self, 'kwargs', None)
|
|
107
|
+
get_result = arguments.get(attr_name, None) if arguments else None
|
|
108
|
+
return get_result
|
|
109
|
+
|
|
110
|
+
def get_all_arguments(self) -> Optional[dict]:
|
|
111
|
+
"""Returns all the arguments passed to the constructor as a dictionary"""
|
|
112
|
+
get_all_arguments_result = getattr(self, 'kwargs', None)
|
|
113
|
+
return get_all_arguments_result
|
|
114
|
+
|
|
115
|
+
def to_json(self) -> str:
|
|
116
|
+
"""This is backwards compatible with the previous implementation"""
|
|
117
|
+
to_json_result = self.to_json_str()
|
|
118
|
+
return to_json_result
|
|
119
|
+
|
|
120
|
+
def to_json_dict(self) -> dict:
|
|
121
|
+
"""Returns a json dict representation of this object"""
|
|
122
|
+
json_str = self.to_json_str()
|
|
123
|
+
json_dict = json.loads(json_str)
|
|
124
|
+
|
|
125
|
+
return json_dict
|
|
126
|
+
|
|
127
|
+
def to_json_str(self) -> str:
|
|
128
|
+
"""Returns a json string representation of this object"""
|
|
129
|
+
to_json_str_result = json.dumps(self.__dict__, default=self._serialize)
|
|
130
|
+
return to_json_str_result
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _serialize(obj: Any) -> str:
|
|
134
|
+
"""Custom serialization function for unsupported types. Used by json.dumps"""
|
|
135
|
+
if isinstance(obj, datetime):
|
|
136
|
+
return obj.isoformat() # Converts datetime to ISO 8601 string
|
|
137
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
|
138
|
+
|
|
139
|
+
def from_json(self, json_string: str) -> 'OurObject':
|
|
140
|
+
"""Returns an instance of the class from a json string"""
|
|
141
|
+
self.__dict__ = json.loads(json_string)
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def __eq__(self, other: object) -> bool:
|
|
145
|
+
"""Checks if two objects are equal"""
|
|
146
|
+
if not isinstance(other, OurObject):
|
|
147
|
+
return False
|
|
148
|
+
eq_result = self.__dict__ == other.__dict__
|
|
149
|
+
return eq_result
|
|
150
|
+
|
|
151
|
+
def __ne__(self, other: object) -> bool:
|
|
152
|
+
"""Checks if two objects are not equal"""
|
|
153
|
+
ne_result = not self.__eq__(other)
|
|
154
|
+
return ne_result
|
|
155
|
+
|
|
156
|
+
def __str__(self) -> str:
|
|
157
|
+
# BEHAVIOUR NOTE: same TypeError-on-None as get_id_column_name.
|
|
158
|
+
# __repr__ right below already guarded for it; __str__ did not.
|
|
159
|
+
str_result = str(self.entity_name) + ": " + str(self.__dict__)
|
|
160
|
+
return str_result
|
|
161
|
+
|
|
162
|
+
def __repr__(self) -> str:
|
|
163
|
+
"""String representation for debugging."""
|
|
164
|
+
# Ensure both entity_name and __dict__ are not None for proper representation
|
|
165
|
+
entity_name = self.entity_name or "OurObject"
|
|
166
|
+
dict_str = str(self.__dict__) if self.__dict__ else "{}"
|
|
167
|
+
repr_result = f"{entity_name}: {dict_str}"
|
|
168
|
+
return repr_result
|
|
169
|
+
|
|
170
|
+
def to_dict(self) -> dict:
|
|
171
|
+
return self.__dict__
|
|
172
|
+
|
|
173
|
+
# TODO This returned python-sdk-remote's version under a docstring
|
|
174
|
+
# admitting that is wrong for derived classes; it now returns
|
|
175
|
+
# our-object-local's, which is wrong in exactly the same way. Retire it,
|
|
176
|
+
# or resolve the subclass's real distribution? Asked on contact #60 -
|
|
177
|
+
# left behaving as before until answered.
|
|
178
|
+
def get_package_version(self) -> str:
|
|
179
|
+
"""Get package version.
|
|
180
|
+
|
|
181
|
+
Note: Returns the version of our-object-local, not the derived class
|
|
182
|
+
package.
|
|
183
|
+
"""
|
|
184
|
+
return PACKAGE_VERSION
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PACKAGE_VERSION = "0.0.1"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: our-object-local
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: PyPI our-object-local Python Package owned by Circlez.ai
|
|
5
|
+
Home-page: https://github.com/circles-zone/our-object-local-python-package
|
|
6
|
+
Author: Circles
|
|
7
|
+
Author-email: info@circlez.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: author-email
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: summary
|
|
18
|
+
|
|
19
|
+
PyPI our-object-local Python Package owned by Circlez.ai
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
our_object_local.egg-info/PKG-INFO
|
|
5
|
+
our_object_local.egg-info/SOURCES.txt
|
|
6
|
+
our_object_local.egg-info/dependency_links.txt
|
|
7
|
+
our_object_local.egg-info/top_level.txt
|
|
8
|
+
our_object_local/src/__init__.py
|
|
9
|
+
our_object_local/src/our_object.py
|
|
10
|
+
our_object_local/src/py.typed
|
|
11
|
+
our_object_local/src/version.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
our_object_local
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# This file should be in the future instead of setup.py
|
|
2
|
+
# https://python-poetry.org/docs/pyproject
|
|
3
|
+
# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license
|
|
4
|
+
|
|
5
|
+
# This file is mandatory for the `poetry version patch`
|
|
6
|
+
|
|
7
|
+
[build-system]
|
|
8
|
+
requires = ["setuptools>=61.0"]
|
|
9
|
+
build-backend = "setuptools.build_meta"
|
|
10
|
+
|
|
11
|
+
[tool.pytest.ini_options]
|
|
12
|
+
pythonpath = ["."]
|
|
13
|
+
|
|
14
|
+
[tool.poetry]
|
|
15
|
+
name = "our-object-local"
|
|
16
|
+
# I believe we are still using the version from setup.py and not from here until Potery will work
|
|
17
|
+
version = "0.0.1" # https://pypi.org/project/our-object-local/
|
|
18
|
+
description = "our-object-local Python Package"
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
authors = [
|
|
21
|
+
"Circlez.ai <info@circlez.ai>",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.poetry.dev-dependencies]
|
|
25
|
+
pytest = "^8.0"
|
|
26
|
+
pytest-cov = "^5.0"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
|
|
3
|
+
# Used by pypa/gh-action-pypi-publish. Lowercase, no underlines, no "main",
|
|
4
|
+
# without the -python-package suffix.
|
|
5
|
+
PACKAGE_NAME = "our-object-local"
|
|
6
|
+
|
|
7
|
+
# The import name, i.e. "import our_object_local". It matches PACKAGE_NAME and
|
|
8
|
+
# the on-disk directory, as every other package does.
|
|
9
|
+
package_dir = PACKAGE_NAME.replace("-", "_")
|
|
10
|
+
|
|
11
|
+
PACKAGE_DESCRIPTION = f"PyPI {PACKAGE_NAME} Python Package owned by Circlez.ai"
|
|
12
|
+
|
|
13
|
+
setuptools.setup(
|
|
14
|
+
name=PACKAGE_NAME,
|
|
15
|
+
# Beta tag suffixed with the Jira Work Item ID, otherwise PR number, so a version traces back to the PR that generated it.
|
|
16
|
+
version='0.0.1', # https://pypi.org/project/our-object-local#history
|
|
17
|
+
author="Circles",
|
|
18
|
+
author_email="info@circlez.ai",
|
|
19
|
+
description=PACKAGE_DESCRIPTION,
|
|
20
|
+
long_description=PACKAGE_DESCRIPTION,
|
|
21
|
+
long_description_content_type='text/markdown',
|
|
22
|
+
url=f"https://github.com/circles-zone/{PACKAGE_NAME}-python-package",
|
|
23
|
+
packages=[package_dir],
|
|
24
|
+
package_dir={package_dir: f'{package_dir}/src'},
|
|
25
|
+
# py.typed marks the package as typed (PEP 561), so mypy reads these
|
|
26
|
+
# annotations in every consumer instead of falling back to Any.
|
|
27
|
+
package_data={package_dir: ['*.py', 'py.typed']},
|
|
28
|
+
classifiers=[
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
],
|
|
32
|
+
# Deliberately empty. This is the bottom of the object model: adding any
|
|
33
|
+
# circlez package here recreates the cycle that sent OurObject into its own
|
|
34
|
+
# repo -- our-object-field-local -> fields-local -> python-sdk-remote ->
|
|
35
|
+
# our-object-local.
|
|
36
|
+
install_requires=[]
|
|
37
|
+
)
|