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.
@@ -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.
@@ -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
+ 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )