-
-
Notifications
You must be signed in to change notification settings - Fork 900
Expand file tree
/
Copy pathbsdd_json.py
More file actions
249 lines (209 loc) · 9.23 KB
/
bsdd_json.py
File metadata and controls
249 lines (209 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
from __future__ import annotations
import json
import weakref
from datetime import datetime
from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator
from .type_hints import *
def _lower_first(s: str) -> str:
return s[:1].lower() + s[1:] if s else s
class CaseInsensitiveModel(BaseModel):
model_config = ConfigDict(populate_by_name=True, alias_generator=_lower_first)
class BsddDictionary(CaseInsensitiveModel):
OrganizationCode: str
DictionaryCode: str
DictionaryVersion: str
LanguageIsoCode: LANGUAGE_ISO_CODE
LanguageOnly: bool
UseOwnUri: bool
DictionaryName: Optional[str] = None
DictionaryUri: Optional[str] = None
License: Optional[str] = "MIT"
LicenseUrl: Optional[str] = None
ChangeRequestEmailAddress: Optional[str] = None
ModelVersion: Optional[str] = "2.0"
MoreInfoUrl: Optional[str] = None
QualityAssuranceProcedure: Optional[str] = None
QualityAssuranceProcedureUrl: Optional[str] = None
ReleaseDate: Optional[datetime] = None
Status: Optional[STATUS] = None
Classes: list[BsddClass] = Field(default_factory=list)
Properties: list[BsddProperty] = Field(default_factory=list)
@classmethod
def load(cls, path) -> BsddDictionary:
"""Load from a JSON file and validate via the normalizer above."""
with open(path, "r", encoding="utf-8") as f:
raw = json.load(f)
# The model_validator(before) handles list/dict/nested shapes
return cls.model_validate(raw)
def save(self, path):
with open(path, "w") as file:
json.dump(self.model_dump(mode="json", exclude_none=True), file)
# add Parent to children after loading
def model_post_init(self, context):
for c in self.Classes:
c._set_parent(self)
for p in self.Properties:
p._set_parent(self)
class BsddClass(CaseInsensitiveModel):
Code: str
Name: str
ClassType: CLASS_TYPE = "Class"
Definition: Optional[str] = None
Description: Optional[str] = None
ParentClassCode: Optional[str | None] = None
RelatedIfcEntityNamesList: Optional[list[str]] = None
Synonyms: Optional[list[str]] = None
ActivationDateUtc: Optional[datetime] = None
ReferenceCode: Optional[str] = None
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
CountryOfOrigin: Optional[COUNTRY_CODE] = None
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
DeActivationDateUtc: Optional[datetime] = None
DeprecationExplanation: Optional[str] = None
DocumentReference: Optional[str] = None
OwnedUri: Optional[str] = None
ReplacedObjectCodes: Optional[list[str]] = None
ReplacingObjectCodes: Optional[list[str]] = None
RevisionDateUtc: Optional[datetime] = None
RevisionNumber: Optional[int] = None
Status: Optional[CLASS_STATUS] = None
SubdivisionsOfUse: Optional[list[str]] = None
Uid: Optional[str] = None
VersionDateUtc: Optional[datetime] = None
VersionNumber: Optional[int] = None
VisualRepresentationUri: Optional[str] = None
ClassProperties: list[BsddClassProperty] = Field(default_factory=list)
ClassRelations: list[BsddClassRelation] = Field(default_factory=list)
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
def _set_parent(self, parent: BsddDictionary) -> None:
self._parent_ref = weakref.ref(parent)
def parent(self) -> Optional[BsddDictionary]:
return self._parent_ref() if self._parent_ref is not None else None
def model_post_init(self, context):
for c in self.ClassProperties:
c._set_parent(self)
for cr in self.ClassRelations:
cr._set_parent(self)
class BsddClassRelation(CaseInsensitiveModel):
RelationType: CLASS_RELATION_TYPE
RelatedClassUri: str
RelatedClassName: Optional[str] = None
Fraction: Optional[float] = None
OwnedUri: Optional[str] = None
def _set_parent(self, parent: BsddClass) -> None:
self._parent_ref = weakref.ref(parent)
def parent(self) -> Optional[BsddClass]:
return self._parent_ref() if self._parent_ref is not None else None
class BsddAllowedValue(CaseInsensitiveModel):
Code: str
Value: str
Description: Optional[str] = None
Uri: Optional[str] = None
SortNumber: Optional[int] = None
OwnedUri: Optional[str] = None
class BsddClassProperty(CaseInsensitiveModel):
Code: str
PropertyCode: Optional[str] = None
PropertyUri: Optional[str] = None
Description: Optional[str] = None
PropertySet: Optional[str] = None
Unit: Optional[str] = None
PredefinedValue: Optional[str] = None
IsRequired: Optional[bool] = None
IsWritable: Optional[bool] = None
MaxExclusive: Optional[float] = None
MaxInclusive: Optional[float] = None
MinExclusive: Optional[float] = None
MinInclusive: Optional[float] = None
Pattern: Optional[str] = None
OwnedUri: Optional[str] = None
PropertyType: Optional[Literal["Property", "Dependency"]] = None
SortNumber: Optional[int] = None
Symbol: Optional[str] = None
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
_parent_ref: Optional[weakref.ReferenceType[BsddClass]] = PrivateAttr(default=None)
def _set_parent(self, parent: BsddClass) -> None:
self._parent_ref = weakref.ref(parent)
def parent(self) -> Optional[BsddClass]:
return self._parent_ref() if self._parent_ref is not None else None
@model_validator(mode="after")
def _validate_property_code_or_uri(self):
"""
only one of PropertyCode or PropertyUri must be set (XOR)
"""
# normalize whitespace
code = self.PropertyCode.strip() if self.PropertyCode and isinstance(self.PropertyCode, str) else None
uri = self.PropertyUri.strip() if self.PropertyUri and isinstance(self.PropertyUri, str) else None
# XOR: exactly one must be provided
if bool(code) == bool(uri):
raise ValueError("Exactly one of PropertyCode or PropertyUri must be provided (not both, not neither)")
# assign normalized values back
object.__setattr__(self, "PropertyCode", code)
object.__setattr__(self, "PropertyUri", uri)
return self
class BsddProperty(CaseInsensitiveModel):
Code: str
Name: str
Definition: Optional[str] = None
Description: Optional[str] = None
DataType: Optional[DATATYPE_TYPE] = None
Units: Optional[list[UNITS_TYPE]] = None
Example: Optional[str] = None
ActivationDateUtc: Optional[datetime] = None
ConnectedPropertyCodes: Optional[list[str]] = None
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
CountryOfOrigin: Optional[COUNTRY_CODE] = None
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
DeActivationDateUtc: Optional[datetime] = None
DeprecationExplanation: Optional[str] = None
Dimension: Optional[str] = None
DimensionLength: Optional[int] = None
DimensionMass: Optional[int] = None
DimensionTime: Optional[int] = None
DimensionElectricCurrent: Optional[int] = None
DimensionThermodynamicTemperature: Optional[int] = None
DimensionAmountOfSubstance: Optional[int] = None
DimensionLuminousIntensity: Optional[int] = None
DocumentReference: Optional[DOCUMENT_TYPE] = None
DynamicParameterPropertyCodes: Optional[list[str]] = None
IsDynamic: Optional[bool] = None
MaxExclusive: Optional[float] = None
MaxInclusive: Optional[float] = None
MinExclusive: Optional[float] = None
MinInclusive: Optional[float] = None
MethodOfMeasurement: Optional[str] = None
OwnedUri: Optional[str] = None
Pattern: Optional[str] = None
PhysicalQuantity: Optional[str] = None
PropertyValueKind: Optional[PROPERTY_VALUE_KIND_TYPE] = None
ReplacedObjectCodes: Optional[list[str]] = None
ReplacingObjectCodes: Optional[list[str]] = None
RevisionDateUtc: Optional[datetime] = None
RevisionNumber: Optional[int] = None
Status: Optional[PROPERTY_STATUS] = None
SubdivisionsOfUse: Optional[list[str]] = None
TextFormat: Optional[str] = None
Uid: Optional[str] = None
VersionDateUtc: Optional[datetime] = None
VersionNumber: Optional[int] = None
VisualRepresentationUri: Optional[str] = None
PropertyRelations: list[BsddPropertyRelation] = Field(default_factory=list)
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
def _set_parent(self, parent: BsddDictionary) -> None:
self._parent_ref = weakref.ref(parent)
def parent(self) -> Optional[BsddDictionary]:
return self._parent_ref() if self._parent_ref is not None else None
def model_post_init(self, context):
for pr in self.PropertyRelations:
pr._set_parent(self)
class BsddPropertyRelation(CaseInsensitiveModel):
RelatedPropertyName: Optional[str] = None
RelatedPropertyUri: str
RelationType: PROPERTY_RELATION_TYPE
OwnedUri: Optional[str] = None
def _set_parent(self, parent: BsddProperty) -> None:
self._parent_ref = weakref.ref(parent)
def parent(self) -> Optional[BsddProperty]:
return self._parent_ref() if self._parent_ref is not None else None