bruce-models 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,10 @@
1
+ # Include README and LICENSE files.
2
+ include README.md
3
+ include LICENSE
4
+
5
+ # Include all files in a specific folder.
6
+ recursive-include bruce_models
7
+
8
+ # Exclude unwanted files.
9
+ exclude *.pyc
10
+ exclude public
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.1
2
+ Name: bruce-models
3
+ Version: 0.0.1
4
+ Summary: This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.
5
+ Author: Matvey Lavrinovich
6
+ Author-email: matveylavrinovich@gmail.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Intended Audience :: Developers
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.0
13
+
14
+ # bruce-models
15
+
16
+ This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.
17
+
18
+ ### Local build
19
+
20
+ - `pip install .`
21
+
22
+ - `python ./public/main.py` (to test it loads after build)
23
+
24
+ ### Test a release build
25
+
26
+ - `python -m build`
@@ -0,0 +1,13 @@
1
+ # bruce-models
2
+
3
+ This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.
4
+
5
+ ### Local build
6
+
7
+ - `pip install .`
8
+
9
+ - `python ./public/main.py` (to test it loads after build)
10
+
11
+ ### Test a release build
12
+
13
+ - `python -m build`
@@ -0,0 +1,15 @@
1
+ from .api.api import Api
2
+ from .api.bruce_api import BruceApi
3
+ from .api.guardian_api import GuardianApi
4
+ from .scenario.scenario import Scenario
5
+ from .entity.entity import Entity
6
+ from .entity.entity_type import EntityType
7
+
8
+ __all__ = [
9
+ "Api"
10
+ "BruceApi",
11
+ "GuardianApi",
12
+ "Scenario",
13
+ "Entity",
14
+ "EntityType"
15
+ ]
@@ -0,0 +1,3 @@
1
+ from .api import Api
2
+ from .bruce_api import BruceApi
3
+ from .guardian_api import GuardianApi
@@ -0,0 +1,18 @@
1
+ import requests
2
+ from typing import Dict
3
+
4
+ class Api:
5
+ @staticmethod
6
+ def parse_result(result: requests.Response) -> Dict:
7
+ """
8
+ Parses the result of an API request to a Nextspace endpoint.
9
+ """
10
+ if result.status_code >= 400 or result.status_code < 200:
11
+ error = result.json()
12
+ raise Exception(error)
13
+
14
+ # Technically, our API can return other types such as a raw CSV file.
15
+ # However, it is very rare.
16
+ # If we need to update this method, we can check for the type and wrap non-json (CSV) in a dict.
17
+
18
+ return result.json()
@@ -0,0 +1,118 @@
1
+ import requests
2
+ from typing import Optional, Dict, TypedDict
3
+ import threading
4
+ from bruce_models import Api
5
+ from bruce_models import GuardianApi
6
+
7
+ class BruceApiParams(TypedDict):
8
+ """
9
+ Represents the parameters for the Bruce API.
10
+ """
11
+ base_url: Optional[str]
12
+ session_id: Optional[str]
13
+ account_id: Optional[str]
14
+ env: Optional[str]
15
+ guardian: Optional[GuardianApi]
16
+
17
+ class BruceApi:
18
+ """
19
+ A basic API wrapper for making HTTP requests.
20
+ """
21
+ def __init__(self, params: BruceApiParams):
22
+ self._loading = True
23
+ self._lock = threading.Lock()
24
+ self._init(params)
25
+
26
+ @property
27
+ def loading(self) -> bool:
28
+ return self._loading
29
+
30
+ def _init(self, params):
31
+ try:
32
+ # If no env was supplied, set to PROD.
33
+ self.env = params.get("env", "PROD")
34
+ self.base_url = params.get("base_url")
35
+ self.session_id = params.get("session_id")
36
+ self.account_id = params.get("account_id")
37
+
38
+ # If no base_url was supplied, but an account_id was, we can request for it.
39
+ if not self.base_url and self.account_id:
40
+ # Use Guardian instance if was supplied, if not, create one.
41
+ guardian = params.get("guardian", GuardianApi({ "env": self.env, "session_id": self.session_id }))
42
+ account = guardian.GET(f"account/{self.account_id}")
43
+ self.base_url = account.get("URL").get("Base")
44
+ elif not self.base_url:
45
+ raise ValueError("A valid base URL or account ID is required.")
46
+
47
+ self._loading = False
48
+ except Exception as e:
49
+ self._loading = False
50
+ raise e
51
+
52
+ def set_session_id(self, session_id: str):
53
+ """
54
+ Sets the x-sessionid header for all future requests.
55
+ """
56
+ self.session_id = session_id
57
+
58
+ def get_env(self):
59
+ """
60
+ Returns the current environment.
61
+ """
62
+ return self.env
63
+
64
+ def _build_headers(self) -> Dict[str, str]:
65
+ """
66
+ Builds the headers for all requests that are sent.
67
+ """
68
+ headers = {
69
+ "Content-Type": "application/json"
70
+ }
71
+ if self.session_id:
72
+ headers["x-sessionid"] = self.session_id
73
+ return headers
74
+
75
+ def get(self, url: str, params: dict = {}) -> Dict:
76
+ """
77
+ Sends a GET request to the API.
78
+ The URL should be absolute.
79
+ """
80
+ self._wait_for_loading()
81
+ response = requests.get(url, headers=self._build_headers(), params=params)
82
+ return Api.parse_result(response)
83
+
84
+ def GET(self, url: str, params: dict = {}) -> Dict:
85
+ """
86
+ Sends a GET request to the API.
87
+ The URL should be relative to the base URL.
88
+ """
89
+ self._wait_for_loading()
90
+ full = f"{self.base_url}/{url.lstrip('/')}"
91
+ return self.get(full, params)
92
+
93
+ def post(self, url: str, data: dict = {}, params: dict = {}) -> Dict:
94
+ """
95
+ Sends a POST request to the API.
96
+ The URL should be absolute.
97
+ """
98
+ self._wait_for_loading()
99
+ response = requests.post(url, headers=self._build_headers(), json=data, params=params)
100
+ return Api.parse_result(response)
101
+
102
+ def POST(self, url: str, data: dict = {}, params: dict = {}) -> Dict:
103
+ """
104
+ Sends a POST request to the API.
105
+ The URL should be relative to the base URL.
106
+ """
107
+ self._wait_for_loading()
108
+ full = f"{self.base_url}/{url.lstrip('/')}"
109
+ return self.post(full, data, params)
110
+
111
+ def _wait_for_loading(self):
112
+ """
113
+ Blocks further requests until the loading state is set to False.
114
+ This method will prevent further requests until initialization is complete.
115
+ """
116
+ with self._lock:
117
+ while self._loading:
118
+ pass
@@ -0,0 +1,98 @@
1
+ import requests
2
+ from typing import Optional, Dict, TypedDict
3
+ import threading
4
+ from bruce_models import Api
5
+
6
+ class GuardianApiParams(TypedDict):
7
+ """
8
+ Represents the parameters for the Guardian API.
9
+ """
10
+ base_url: Optional[str]
11
+ session_id: Optional[str]
12
+ env: Optional[str]
13
+
14
+ class GuardianApi:
15
+ """
16
+ A basic API wrapper for making HTTP requests.
17
+ """
18
+ def __init__(self, params: GuardianApiParams):
19
+ self._loading = True
20
+ self._lock = threading.Lock()
21
+ self._init(params)
22
+
23
+ @property
24
+ def loading(self) -> bool:
25
+ return self._loading
26
+
27
+ def _init(self, params):
28
+ try:
29
+ # If no env was supplied, set to PROD.
30
+ self.env = params.get("env", "PROD")
31
+ self.base_url = params.get("base_url")
32
+ self.session_id = params.get("session_id")
33
+
34
+ # If no base_url was supplied, we'll calculate it based on the env.
35
+ if not self.base_url:
36
+ if self.env == "DEV":
37
+ self.base_url = "https://guardian.nextspace-dev.net/"
38
+ elif self.env == "STG":
39
+ self.base_url = "https://guardian.nextspace-stg.net/"
40
+ elif self.env == "UAT":
41
+ self.base_url = "https://guardan.nextspace-uat.net/"
42
+ else:
43
+ self.base_url = "https://guardian.nextspace.host/"
44
+
45
+ self._loading = False
46
+ except Exception as e:
47
+ self._loading = False
48
+ raise e
49
+
50
+ def set_session_id(self, session_id: str):
51
+ """
52
+ Sets the x-sessionid header for all future requests.
53
+ """
54
+ self.session_id = session_id
55
+
56
+ def get_env(self):
57
+ """
58
+ Returns the current environment.
59
+ """
60
+ return self.env
61
+
62
+ def _build_headers(self) -> Dict[str, str]:
63
+ """
64
+ Builds the headers for all requests that are sent.
65
+ """
66
+ headers = {
67
+ "Content-Type": "application/json"
68
+ }
69
+ if self.session_id:
70
+ headers["x-sessionid"] = self.session_id
71
+ return headers
72
+
73
+ def get(self, url: str) -> Dict:
74
+ """
75
+ Sends a GET request to the API.
76
+ The URL should be absolute.
77
+ """
78
+ self._wait_for_loading()
79
+ response = requests.get(url, headers=self._build_headers())
80
+ return Api.parse_result(response)
81
+
82
+ def GET(self, url: str) -> Dict:
83
+ """
84
+ Sends a GET request to the API.
85
+ The URL should be relative to the base URL.
86
+ """
87
+ self._wait_for_loading()
88
+ full = f"{self.base_url}/{url.lstrip('/')}"
89
+ return self.get(full)
90
+
91
+ def _wait_for_loading(self):
92
+ """
93
+ Blocks further requests until the loading state is set to False.
94
+ This method will prevent further requests until initialization is complete.
95
+ """
96
+ with self._lock:
97
+ while self._loading:
98
+ pass
@@ -0,0 +1,2 @@
1
+ from .entity import Entity
2
+ from .entity_type import EntityType
@@ -0,0 +1,150 @@
1
+ from typing import Optional, TypedDict, List, Any
2
+ from bruce_models import BruceApi
3
+
4
+ class JEntityInternal(TypedDict):
5
+ """
6
+ Represents the Nextspace internal properties defined under the 'Bruce' key of an Entity record.
7
+ """
8
+
9
+ # ID of the entity.
10
+ # If unset an ID will be generated during the Update request.
11
+ ID: Optional[str]
12
+ # Name of the Entity.
13
+ # This can be set directly, or the Entity Type can be configured to use a specific attribute.
14
+ # If the attribute is changed, a re-index should be launched to update the name.
15
+ Name: Optional[str]
16
+ # Nextspace internal ID.
17
+ # Readonly and created on Entity creation.
18
+ InternalID: Optional[int]
19
+
20
+ # Entity Type ID.
21
+ EntityType_ID: str
22
+
23
+ # ID of the user who created the entity.
24
+ # Will be set automatically on creation.
25
+ CreatedBy_User_ID: Optional[str]
26
+ # Created date/time of the entity.
27
+ CreatedTime: Optional[Any]
28
+ # Array of associated tags (Used to be called layers).
29
+ # Update to change what tags are associated. Exclude from data to keep it the same.
30
+ Layer_ID: Optional[List[int]]
31
+
32
+ # Scenario this is related to. Typically the Key, but if the record isn't found then it will be the ID.
33
+ # When present, this indicates this is a variant of the primary record and not the primary record.
34
+ Scenario: Optional[str | int]
35
+
36
+ # If we're retrieving historic records, then these values are populated,
37
+ # to indicate the historic data key and date-time.
38
+ HistoricAttrKey: Optional[str]
39
+ HistoricDateTime: Optional[str]
40
+
41
+ # Marker that indicates the entity is a calculated alternative schema.
42
+ # This is used to indicate that the entity is read-only and should not be saved.
43
+ SchemaID: Optional[str]
44
+
45
+ # Entity relative position to parent within an assembly.
46
+ AssemblyPosition: Optional[List[List[float]]]
47
+
48
+ # Entity location in degrees/meters.
49
+ # This is typically relative to the active scene's terrain.
50
+ # However what its relative to is dictated by the Entity Style during rendering.
51
+ # This dictates where 3D models are placed, in the absence of VectorGeometry-
52
+ #this is used to place vector points as well.
53
+ Location: Optional[Any]
54
+ # These are applied only to the Entity's LOD and not the VectorGeometry.
55
+ Transform: Optional[Any]
56
+ # Entity boundaries in degrees/meters.
57
+ Boundaries: Optional[Any]
58
+ # Entity vector geometry. Eg: a point, polyline, or polygon to draw.
59
+ VectorGeometry: Optional[Any]
60
+
61
+ class Entity:
62
+ """
63
+ Represents an Entity in Nextspace.
64
+ You will find the JEntity data-model and all related API communication here.
65
+ """
66
+
67
+ class JEntity(TypedDict, total=False):
68
+ """
69
+ Represents the JSON structure of an Entity.
70
+ """
71
+ Bruce: JEntityInternal
72
+ __additional__: Optional[dict[str, Any]]
73
+
74
+ @staticmethod
75
+ def get(api: BruceApi, entity_id: str = "") -> JEntity:
76
+ """
77
+ Retrieves an Entity by its ID.
78
+ """
79
+ if not entity_id:
80
+ raise ValueError("A valid Entity ID is required.")
81
+ return api.GET(f"v3/entity/{entity_id}")
82
+
83
+ @staticmethod
84
+ def get(api: BruceApi, entity_id: str = "", scenario: Optional[str | int] = 0) -> JEntity:
85
+ """
86
+ Retrieves an Entity by its ID and Scenario.
87
+ """
88
+ if not entity_id:
89
+ raise ValueError("A valid Entity ID is required.")
90
+ return api.GET(f"v3/entity/{entity_id}?Scenario={scenario}")
91
+
92
+ @staticmethod
93
+ def get_list(
94
+ api: BruceApi,
95
+ entity_type_id: str = "",
96
+ skip: int = 0,
97
+ take: int = 50,
98
+ scenario: Optional[str | int] = 0
99
+ ) -> List[JEntity]:
100
+ """
101
+ Retrieves a list of Entities.
102
+ """
103
+ params = {
104
+ "Type": entity_type_id,
105
+ "PageIndex": skip,
106
+ "PageSize": take,
107
+ "Scenario": scenario
108
+ }
109
+ data = api.GET("v3/entities", params)
110
+ return data["Items"]
111
+
112
+ @staticmethod
113
+ def update(
114
+ api: BruceApi,
115
+ # Entity to update/create.
116
+ entity: JEntityInternal,
117
+ # (Optional) Scenario to update the Entity under.
118
+ # It can be specified here, or within the Entity itself.
119
+ # If specified here, it will override the Entity's Scenario.
120
+ scenario: Optional[str | int] = 0
121
+ ) -> JEntity:
122
+ """
123
+ Updates or creates an Entity.
124
+ If an Entity does not have an ID, a new record is made.
125
+ """
126
+ params = {
127
+ "Scenario": scenario
128
+ }
129
+ return api.POST("v3/entity", entity, params)
130
+
131
+ @staticmethod
132
+ def update_list(
133
+ api: BruceApi,
134
+ # List of Entities to update/create.
135
+ entities: List[JEntityInternal],
136
+ # (Optional) Scenario to update the Entities under.
137
+ # It can be specified here, or within the Entity itself.
138
+ # If specified here, it will override the Entity's Scenario.
139
+ scenario: Optional[str | int] = 0
140
+ ) -> List[JEntity]:
141
+ """
142
+ Updates or creates a list of Entities.
143
+ If an Entity does not have an ID, a new record is made.
144
+ """
145
+ body = {
146
+ "Items": entities,
147
+ "Scenario": scenario
148
+ }
149
+ data = api.POST("v3/entities", body)
150
+ return data["Items"]
@@ -0,0 +1,52 @@
1
+ from typing import Optional, TypedDict, List, Any
2
+ from bruce_models import BruceApi
3
+
4
+ class EntityType:
5
+ """
6
+ Represents an Entity Type in Nextspace.
7
+ You will find the JEntityType data-model and all related API communication here.
8
+ """
9
+
10
+ class JEntityType(TypedDict):
11
+ # ID of the Entity Type.
12
+ ID: Optional[str]
13
+ # Human readable name of the Entity Type.
14
+ Name: str;
15
+ # Human readable description of the Entity Type.
16
+ Description: Optional[str]
17
+ # If Entity Type access (and Entity access) should be restricted.
18
+ # When an Entity Type is restricted a user must have the "EntityType_<typeId>" permission.
19
+ IsAccessRestricted: Optional[bool]
20
+ # The data schema defining expected attributes for entities to have.
21
+ DataSchema: Optional[Any]
22
+ # Attribute that defines Entity names in the Entity Type.
23
+ # If not supplied, then the Entities can have names set directly.
24
+ # It is a comma separated array of attribute string paths.
25
+ DisplayNameAttributePath: Optional[str]
26
+ # If this schema is "strict". If Entities in an Entity Type are different enough between each-other then the schema is not strict.
27
+ # This will dictate how UI will display the attributes.
28
+ IsStrictSchema: Optional[bool]
29
+ # Default Style for the corresponding Entities.
30
+ DisplaySettings_ID: Optional[int]
31
+ # Last updated date/time.
32
+ LastUpdate_Time: Optional[Any]
33
+ # ID of the parent Entity Type (if any).
34
+ # This is used for organization. When a parent type is deleted, the child types are also deleted.
35
+ Parent_EntityType_ID: Optional[str]
36
+
37
+ @staticmethod
38
+ def get(api: BruceApi, id: str) -> JEntityType:
39
+ """
40
+ Get an Entity Type by its ID.
41
+ """
42
+ if not id:
43
+ raise ValueError("ID is required to get an Entity Type.")
44
+ return api.GET("entitytype", id)
45
+
46
+ @staticmethod
47
+ def get_list(api: BruceApi) -> List[JEntityType]:
48
+ """
49
+ Get a list of all Entity Types.
50
+ """
51
+ data = api.GET("entitytypes")
52
+ return data["Items"]
@@ -0,0 +1 @@
1
+ from .scenario import Scenario
@@ -0,0 +1,43 @@
1
+ from typing import Optional, TypedDict
2
+ from bruce_models import BruceApi
3
+
4
+ class Scenario:
5
+ """
6
+ Represents a Scenario in Nextspace.
7
+ You will find the JScenario data-model and all related API communication here.
8
+ """
9
+
10
+ class JScenario(TypedDict):
11
+ """
12
+ Represents the JSON structure of a Scenario.
13
+ """
14
+
15
+ # Unique numeric identity of the record.
16
+ # Generated by the database.
17
+ ID: Optional[int]
18
+ # (30) Unique keyword that is used for identification of the record when referred to in URL.
19
+ Key: str
20
+ # (120) User friendly name to display in UI.
21
+ DisplayName: str
22
+
23
+ """
24
+ Represents the Scenario model and provides functions to interact with the API.
25
+ """
26
+
27
+ @staticmethod
28
+ def get_by_id(api: BruceApi, scenario_id: int = 0) -> JScenario:
29
+ """
30
+ Retrieves a Scenario by its ID.
31
+ """
32
+ if not scenario_id or scenario_id < 1:
33
+ raise ValueError("A valid scenario ID is required.")
34
+ return api.GET(f"scenario/{scenario_id}")
35
+
36
+ @staticmethod
37
+ def get_by_key(api: BruceApi, scenario_key: str = "") -> JScenario:
38
+ """
39
+ Retrieves a Scenario by its Key.
40
+ """
41
+ if not scenario_key:
42
+ raise ValueError("A valid scenario Key is required.")
43
+ return api.GET(f"scenario/key/{scenario_key}")
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.1
2
+ Name: bruce-models
3
+ Version: 0.0.1
4
+ Summary: This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.
5
+ Author: Matvey Lavrinovich
6
+ Author-email: matveylavrinovich@gmail.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Intended Audience :: Developers
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.0
13
+
14
+ # bruce-models
15
+
16
+ This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.
17
+
18
+ ### Local build
19
+
20
+ - `pip install .`
21
+
22
+ - `python ./public/main.py` (to test it loads after build)
23
+
24
+ ### Test a release build
25
+
26
+ - `python -m build`
@@ -0,0 +1,19 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ bruce_models/__init__.py
6
+ bruce_models.egg-info/PKG-INFO
7
+ bruce_models.egg-info/SOURCES.txt
8
+ bruce_models.egg-info/dependency_links.txt
9
+ bruce_models.egg-info/requires.txt
10
+ bruce_models.egg-info/top_level.txt
11
+ bruce_models/api/__init__.py
12
+ bruce_models/api/api.py
13
+ bruce_models/api/bruce_api.py
14
+ bruce_models/api/guardian_api.py
15
+ bruce_models/entity/__init__.py
16
+ bruce_models/entity/entity.py
17
+ bruce_models/entity/entity_type.py
18
+ bruce_models/scenario/__init__.py
19
+ bruce_models/scenario/scenario.py
@@ -0,0 +1 @@
1
+ requests>=2.0
@@ -0,0 +1 @@
1
+ bruce_models
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="bruce-models",
5
+ version="0.0.1",
6
+ description="This is a library which defines data-models for the Nextspace API and provides functions for communicating with the endpoints.",
7
+ long_description=open("README.md").read(),
8
+ long_description_content_type="text/markdown",
9
+ author="Matvey Lavrinovich",
10
+ author_email="matveylavrinovich@gmail.com",
11
+ packages=find_packages(),
12
+ install_requires=[
13
+ "requests>=2.0"
14
+ ],
15
+ classifiers=[
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Intended Audience :: Developers"
19
+ ],
20
+ python_requires=">=3.6",
21
+ )