Skip to content

Commit 42b17b1

Browse files
Merge pull request #100 from Exabyte-io/update/SOF-7321
chore: add in_memory_entity
2 parents c3755c4 + 220058b commit 42b17b1

File tree

13 files changed

+241
-20
lines changed

13 files changed

+241
-20
lines changed

.babelrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,3 @@
1515
"@babel/plugin-proposal-class-properties"
1616
]
1717
}
18-

.eslintrc.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
{
22
"extends": ["@exabyte-io/eslint-config"]
33
}
4-

.github/workflows/cicd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
path: actions
5757

5858
- name: Run python unit tests
59-
uses: ./actions/py/test
59+
uses: ./actions/py/pytest
6060
with:
6161
python-version: ${{ matrix.python-version }}
6262
unit-test-directory: tests/py/unit

.prettierrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
"trailingComma": "all",
55
"tabWidth": 4
66
}
7-

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,3 @@ npm run transpile
4646
# run tests
4747
npm run test
4848
```
49-

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ classifiers = [
1616
]
1717
dependencies = [
1818
# add requirements here
19-
"numpy"
19+
"numpy",
20+
"jsonschema>=2.6.0",
21+
"mat3ra-utils",
2022
]
2123

2224
[project.optional-dependencies]
@@ -27,7 +29,9 @@ tests = [
2729
"ruff",
2830
"isort",
2931
"mypy",
30-
"pip-tools"
32+
"pip-tools",
33+
"pytest",
34+
"pytest-cov",
3135
]
3236
all = ["mat3ra-code[tests]"]
3337

src/py/mat3ra/code/__init__.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1-
import numpy as np
1+
from typing import Any, Dict, Optional
22

3+
from mat3ra.utils import object as object_utils
34

4-
def get_length(vec: np.ndarray) -> np.float_:
5-
return np.linalg.norm(vec)
5+
6+
class BaseUnderscoreJsonPropsHandler(object):
7+
def __init__(self, config: Dict[str, Any] = {}) -> None:
8+
self._json = object_utils.clone_deep(config)
9+
10+
def __getattribute__(self, item):
11+
try:
12+
default_attribute = super().__getattribute__(item)
13+
return default_attribute
14+
except AttributeError:
15+
return self.__getattribute_from_json__(item)
16+
17+
def __getattribute_from_json__(self, name: str, default_value: Any = None) -> Any:
18+
return self._json.get(name, default_value)
19+
20+
def get_prop(self, name: str, default_value: Any = None) -> Any:
21+
return self.__getattribute_from_json__(name, default_value)
22+
23+
def set_prop(self, name: str, value: Any) -> None:
24+
object_utils.set(self._json, name, value)
25+
26+
def unset_prop(self, name: str) -> None:
27+
del self._json[name]
28+
29+
def set_props(self, json: Dict[str, Any] = {}) -> "BaseUnderscoreJsonPropsHandler":
30+
for key, value in json.items():
31+
self.set_prop(key, value)
32+
return self

src/py/mat3ra/code/constants.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class Coefficients:
2+
EV_TO_RY = 0.0734986176
3+
BOHR_TO_ANGSTROM = 0.52917721092
4+
ANGSTROM_TO_BOHR = 1 / 0.52917721092
5+
EV_A_TO_RY_BOHR = 1 / 25.71104309541616
6+
7+
8+
class Tolerance:
9+
# in crystal coordinates
10+
length = 0.01
11+
lengthAngstrom = 0.001
12+
pointsDistance = 0.001
13+
14+
15+
class Units:
16+
bohr = "bohr"
17+
angstrom = "angstrom"
18+
degree = "degree"
19+
radian = "radian"
20+
alat = "alat"
21+
22+
23+
class AtomicCoordinateUnits:
24+
crystal = "crystal"
25+
cartesian = "cartesian"

src/py/mat3ra/code/entity.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from typing import Any, Dict, List, Optional
2+
3+
import jsonschema
4+
from mat3ra.utils import object as object_utils
5+
6+
from . import BaseUnderscoreJsonPropsHandler
7+
from .mixins import DefaultableMixin, HasDescriptionMixin, HasMetadataMixin, NamedMixin
8+
9+
10+
class ValidationErrorCode:
11+
IN_MEMORY_ENTITY_DATA_INVALID = "IN_MEMORY_ENTITY_DATA_INVALID"
12+
13+
14+
class ErrorDetails:
15+
def __init__(self, error: Optional[Dict[str, Any]], json: Dict[str, Any], schema: Dict):
16+
self.error = error
17+
self.json = json
18+
self.schema = schema
19+
20+
21+
class EntityError(Exception):
22+
def __init__(self, code: ValidationErrorCode, details: Optional[ErrorDetails] = None):
23+
super().__init__(code)
24+
self.code = code
25+
self.details = details
26+
27+
28+
class InMemoryEntity(BaseUnderscoreJsonPropsHandler):
29+
jsonSchema: Optional[Dict] = None
30+
31+
@classmethod
32+
def get_cls(cls) -> str:
33+
return cls.__name__
34+
35+
@property
36+
def cls(self) -> str:
37+
return self.__class__.__name__
38+
39+
def get_cls_name(self) -> str:
40+
return self.__class__.__name__
41+
42+
@classmethod
43+
def create(cls, config: Dict[str, Any]) -> Any:
44+
return cls(config)
45+
46+
def to_json(self, exclude: List[str] = []) -> Dict[str, Any]:
47+
return self.clean(object_utils.clone_deep(object_utils.omit(self._json, exclude)))
48+
49+
def clone(self, extra_context: Dict[str, Any] = {}) -> Any:
50+
return self.__class__.__init__({**self.to_json(), **extra_context})
51+
52+
@staticmethod
53+
def validate_data(data: Dict[str, Any], clean: bool = False):
54+
if clean:
55+
print("Error: clean is not supported for InMemoryEntity.validateData")
56+
if InMemoryEntity.jsonSchema:
57+
jsonschema.validate(data, InMemoryEntity.jsonSchema)
58+
59+
def validate(self) -> None:
60+
if self._json:
61+
self.__class__.validate_data(self._json)
62+
63+
def clean(self, config: Dict[str, Any]) -> Dict[str, Any]:
64+
# Not implemented, consider the below for the implementation
65+
# https://stackoverflow.com/questions/44694835/remove-properties-from-json-object-not-present-in-schema
66+
return config
67+
68+
def is_valid(self) -> bool:
69+
try:
70+
self.validate()
71+
return True
72+
except EntityError:
73+
return False
74+
75+
# Properties
76+
@property
77+
def id(self) -> str:
78+
return self.prop("_id", "")
79+
80+
@id.setter
81+
def id(self, id: str) -> None:
82+
self.set_prop("_id", id)
83+
84+
@property
85+
def slug(self) -> str:
86+
return self.prop("slug", "")
87+
88+
def get_as_entity_reference(self, by_id_only: bool = False) -> Dict[str, str]:
89+
if by_id_only:
90+
return {"_id": self.id}
91+
else:
92+
return {"_id": self.id, "slug": self.slug, "cls": self.get_cls_name()}
93+
94+
95+
class HasDescriptionHasMetadataNamedDefaultableInMemoryEntity(
96+
InMemoryEntity, DefaultableMixin, NamedMixin, HasMetadataMixin, HasDescriptionMixin
97+
):
98+
pass
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import Any, Dict
2+
3+
from .. import BaseUnderscoreJsonPropsHandler
4+
5+
6+
class DefaultableMixin(BaseUnderscoreJsonPropsHandler):
7+
__default_config__: Dict[str, Any]
8+
9+
@property
10+
def is_default(self) -> bool:
11+
return self.get_prop("isDefault", False)
12+
13+
@is_default.setter
14+
def is_default(self, is_default: bool = False) -> None:
15+
self.set_prop("isDefault", is_default)
16+
17+
@classmethod
18+
def create_default(cls) -> "DefaultableMixin":
19+
return cls(cls.__default_config__)
20+
21+
22+
class NamedMixin(BaseUnderscoreJsonPropsHandler):
23+
@property
24+
def name(self) -> str:
25+
return self.get_prop("name", False)
26+
27+
@name.setter
28+
def name(self, name: str = "") -> None:
29+
self.set_prop("name", name)
30+
31+
32+
class HasMetadataMixin(BaseUnderscoreJsonPropsHandler):
33+
@property
34+
def metadata(self) -> Dict:
35+
return self.get_prop("metadata", False)
36+
37+
@metadata.setter
38+
def metadata(self, metadata: Dict = {}) -> None:
39+
self.set_prop("metadata", metadata)
40+
41+
42+
class HasDescriptionMixin(BaseUnderscoreJsonPropsHandler):
43+
@property
44+
def description(self) -> str:
45+
return self.get_prop("description", "")
46+
47+
@description.setter
48+
def description(self, description: str = "") -> None:
49+
self.set_prop("description", description)
50+
51+
@property
52+
def description_object(self) -> str:
53+
return self.get_prop("descriptionObject", "")
54+
55+
@description_object.setter
56+
def description_object(self, description_object: str = "") -> None:
57+
self.set_prop("descriptionObject", description_object)

0 commit comments

Comments
 (0)