heurist-api 0.1.2__py3-none-any.whl
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.
Potentially problematic release.
This version of heurist-api might be problematic. Click here for more details.
- heurist/__init__.py +1 -0
- heurist/api/__init__.py +0 -0
- heurist/api/client.py +122 -0
- heurist/api/connection.py +71 -0
- heurist/api/constants.py +19 -0
- heurist/api/credentials.py +71 -0
- heurist/api/exceptions.py +45 -0
- heurist/api/url_builder.py +148 -0
- heurist/api/utils.py +24 -0
- heurist/cli/__init__.py +0 -0
- heurist/cli/__main__.py +227 -0
- heurist/cli/load.py +55 -0
- heurist/cli/records.py +49 -0
- heurist/cli/schema.py +94 -0
- heurist/database/__init__.py +3 -0
- heurist/database/basedb.py +125 -0
- heurist/database/database.py +96 -0
- heurist/models/__init__.py +0 -0
- heurist/models/dynamic/__init__.py +3 -0
- heurist/models/dynamic/annotation.py +143 -0
- heurist/models/dynamic/create_model.py +82 -0
- heurist/models/dynamic/date.py +61 -0
- heurist/models/dynamic/type.py +96 -0
- heurist/models/structural/DetailTypes.py +34 -0
- heurist/models/structural/RecStructure.py +27 -0
- heurist/models/structural/RecTypeGroups.py +27 -0
- heurist/models/structural/RecTypes.py +27 -0
- heurist/models/structural/Terms.py +27 -0
- heurist/models/structural/__init__.py +19 -0
- heurist/models/structural/dty.py +121 -0
- heurist/models/structural/hml_structure.py +36 -0
- heurist/models/structural/rst.py +141 -0
- heurist/models/structural/rtg.py +25 -0
- heurist/models/structural/rty.py +81 -0
- heurist/models/structural/trm.py +34 -0
- heurist/models/structural/utils.py +53 -0
- heurist/schema/__init__.py +27 -0
- heurist/schema/models.py +70 -0
- heurist/schema/rel_to_dict.py +39 -0
- heurist/sql/__init__.py +21 -0
- heurist/sql/joinRecordTypeIDNameByGroupType.sql +10 -0
- heurist/sql/joinRecordTypeMetadata.sql +17 -0
- heurist/sql/selectRecordTypeSchema.sql +51 -0
- heurist/sql/sql_safety.py +101 -0
- heurist/utils/constants.py +1 -0
- heurist/utils/rel_to_dict_array.py +8 -0
- heurist/validators/__init__.py +3 -0
- heurist/validators/detail_validator.py +142 -0
- heurist/validators/exceptions.py +34 -0
- heurist/validators/parse_heurist_date.py +71 -0
- heurist/validators/record_validator.py +156 -0
- heurist/workflows/__init__.py +3 -0
- heurist/workflows/etl.py +66 -0
- heurist_api-0.1.2.dist-info/METADATA +453 -0
- heurist_api-0.1.2.dist-info/RECORD +80 -0
- heurist_api-0.1.2.dist-info/WHEEL +4 -0
- heurist_api-0.1.2.dist-info/entry_points.txt +2 -0
- heurist_api-0.1.2.dist-info/licenses/LICENSE +427 -0
- mock_data/__init__.py +22 -0
- mock_data/blocktext/__init__.py +0 -0
- mock_data/blocktext/single.py +7 -0
- mock_data/date/__init__.py +0 -0
- mock_data/date/compound_repeated.py +44 -0
- mock_data/date/compound_single.py +30 -0
- mock_data/date/simple_single.py +16 -0
- mock_data/date/timestamp_repeated.py +30 -0
- mock_data/enum/__init__.py +0 -0
- mock_data/enum/repeated.py +29 -0
- mock_data/enum/single.py +18 -0
- mock_data/file/__init__.py +0 -0
- mock_data/file/single.py +28 -0
- mock_data/float/__init__.py +0 -0
- mock_data/float/single.py +8 -0
- mock_data/freetext/__init__.py +0 -0
- mock_data/freetext/single.py +16 -0
- mock_data/geo/__init__.py +0 -0
- mock_data/geo/single.py +22 -0
- mock_data/resource/__init__.py +0 -0
- mock_data/resource/repeated.py +35 -0
- mock_data/resource/single.py +16 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from heurist.models.dynamic.type import FieldType
|
|
4
|
+
from heurist.sql.sql_safety import SafeSQLName
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PydanticField:
|
|
9
|
+
trm_validation_alias_suffix = "_TRM"
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self, dty_ID: int, rst_DisplayName: str, dty_Type: str, rst_MaxValues: int
|
|
13
|
+
):
|
|
14
|
+
"""
|
|
15
|
+
Using information of 1 detail (data field) from a Heurist record, build a \
|
|
16
|
+
Pydantic data field annotation.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
dty_ID (int): Detail's ID.
|
|
20
|
+
rst_DisplayName (str): Name of the detail displayed in Heurist.
|
|
21
|
+
dty_Type (str): Detail's data type.
|
|
22
|
+
rst_MaxValues (int): Heurist indicator if the detail can be repeated.
|
|
23
|
+
"""
|
|
24
|
+
self.dty_ID = dty_ID
|
|
25
|
+
self.rst_DisplayName = rst_DisplayName
|
|
26
|
+
self.dty_Type = dty_Type
|
|
27
|
+
self.rst_MaxValues = rst_MaxValues
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _get_validation_alias(cls, dty_ID: int) -> str:
|
|
31
|
+
return f"DTY{dty_ID}"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def validation_alias(self) -> str:
|
|
35
|
+
return self._get_validation_alias(dty_ID=self.dty_ID)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def serlialization_alias(self) -> str:
|
|
39
|
+
return SafeSQLName().create_column_name(
|
|
40
|
+
field_name=self.rst_DisplayName, field_type=self.dty_Type
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def pydantic_type(self) -> Any:
|
|
45
|
+
fieldtype = FieldType.to_pydantic(datatype=self.dty_Type)
|
|
46
|
+
if self._is_type_repeatable():
|
|
47
|
+
return list[fieldtype]
|
|
48
|
+
else:
|
|
49
|
+
return fieldtype
|
|
50
|
+
|
|
51
|
+
def build_field(self) -> dict:
|
|
52
|
+
"""
|
|
53
|
+
Build a Pydantic field annotation for a detail whose value will simply be the \
|
|
54
|
+
result of the `RecordDetailConverter`, meaning not a date and not a \
|
|
55
|
+
vocabulary term.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
dict: Pydantic field annotation.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
return self._compose_annotation(
|
|
62
|
+
validation_alias=self.validation_alias,
|
|
63
|
+
serialization_alias=self.serlialization_alias,
|
|
64
|
+
pydantic_type=self.pydantic_type,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def build_term_fk(self) -> dict:
|
|
68
|
+
"""
|
|
69
|
+
Build a Pydantic field annotation for a foreign key reference to the vocabulary\
|
|
70
|
+
term in the constructed database's trm table. This field is written to the \
|
|
71
|
+
Pydantic model in addition to a column for the term that simply has the \
|
|
72
|
+
label.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict: Pydantic field annotation.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
validation_alias = self.validation_alias + self.trm_validation_alias_suffix
|
|
79
|
+
serialization_alias = self.serlialization_alias + " TRM-ID"
|
|
80
|
+
if self._is_type_repeatable():
|
|
81
|
+
pydantic_type = list[Optional[int]]
|
|
82
|
+
else:
|
|
83
|
+
pydantic_type = Optional[int]
|
|
84
|
+
|
|
85
|
+
return self._compose_annotation(
|
|
86
|
+
validation_alias=validation_alias,
|
|
87
|
+
serialization_alias=serialization_alias,
|
|
88
|
+
pydantic_type=pydantic_type,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _is_type_repeatable(self) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Heurist uses the code 0 to indicate that a record's detail (field) \
|
|
94
|
+
can be repeated. Parse this information on the record structure \
|
|
95
|
+
to determine a boolean indicating whether or not the detail is repeated.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
bool: Whether the detail can be repeated.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
if self.rst_MaxValues == 0:
|
|
102
|
+
return True
|
|
103
|
+
else:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
def _compose_annotation(
|
|
107
|
+
self, validation_alias: str, serialization_alias: str, pydantic_type: Any
|
|
108
|
+
) -> dict:
|
|
109
|
+
"""
|
|
110
|
+
Using the Heurist information stored in the class instance's attributes, build \
|
|
111
|
+
the Pydantic data field's alias and compose its annotation.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dict: Key-value pair: Pydantic field's alias (key) - annotation (value)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
if not validation_alias:
|
|
118
|
+
raise ValueError()
|
|
119
|
+
if not serialization_alias:
|
|
120
|
+
raise ValueError()
|
|
121
|
+
if not pydantic_type:
|
|
122
|
+
raise ValueError()
|
|
123
|
+
|
|
124
|
+
if self.rst_MaxValues == 0:
|
|
125
|
+
default = []
|
|
126
|
+
else:
|
|
127
|
+
default = None
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
validation_alias: (
|
|
131
|
+
pydantic_type,
|
|
132
|
+
Field(
|
|
133
|
+
# ID of the column's data type in Heurist
|
|
134
|
+
description=self.dty_ID,
|
|
135
|
+
# Formatted way to identify the Pydantic column
|
|
136
|
+
validation_alias=validation_alias,
|
|
137
|
+
# SQL-safe version of the column name
|
|
138
|
+
serialization_alias=serialization_alias,
|
|
139
|
+
# Default value to write in DuckDB column
|
|
140
|
+
default=default,
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from heurist.models.dynamic.annotation import PydanticField
|
|
2
|
+
from heurist.sql.sql_safety import SafeSQLName
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic import create_model as pydantic_create_model
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HeuristRecord:
|
|
8
|
+
def __init__(self, rty_Name: str, rty_ID: int, detail_metadata: list[dict]):
|
|
9
|
+
self.rty_ID = rty_ID
|
|
10
|
+
# Create an SQL-safe name to give the model when it's serialized to a table
|
|
11
|
+
self.table_name = SafeSQLName().create_table_name(record_name=rty_Name)
|
|
12
|
+
# Create the Pydantic model
|
|
13
|
+
self.model = create_record_type_model(
|
|
14
|
+
model_name=self.table_name, detail_metadata=detail_metadata
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_record_type_model(model_name: str, detail_metadata: list[dict]) -> BaseModel:
|
|
19
|
+
"""
|
|
20
|
+
Each detail's set of metadata must be a dictionary with the following keys:
|
|
21
|
+
dty_ID,
|
|
22
|
+
rst_DisplayName,
|
|
23
|
+
dty_Type,
|
|
24
|
+
rst_MaxValues
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> d1 = {'dty_ID': 1,
|
|
28
|
+
... 'rst_DisplayName': 'label',
|
|
29
|
+
... 'dty_Type': 'enum',
|
|
30
|
+
... 'rst_MaxValues': 1}
|
|
31
|
+
>>> d2 = {'dty_ID': 2,
|
|
32
|
+
... 'rst_DisplayName': 'date',
|
|
33
|
+
... 'dty_Type': 'date',
|
|
34
|
+
... 'rst_MaxValues': 1}
|
|
35
|
+
>>> model = create_record_type_model('test', [d1, d2])
|
|
36
|
+
>>> model.__annotations__.keys()
|
|
37
|
+
dict_keys(['id', 'type', 'DTY1', 'DTY1_TRM', 'DTY2'])
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
model_name (str): The model's name.
|
|
41
|
+
detail_metadata (list[dict]): A list of metadata about each field (detail).
|
|
42
|
+
|
|
43
|
+
Return:
|
|
44
|
+
(BaseModel): Pydantic model.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Indifferent to the record's data fields, set up universal data fields
|
|
48
|
+
# present in every dynamic Pydantic model for records of any type
|
|
49
|
+
kwargs = {
|
|
50
|
+
"id": (
|
|
51
|
+
int,
|
|
52
|
+
Field(
|
|
53
|
+
default=0,
|
|
54
|
+
alias="rec_ID",
|
|
55
|
+
validation_alias="rec_ID",
|
|
56
|
+
serialization_alias="H-ID",
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
"type": (
|
|
60
|
+
int,
|
|
61
|
+
Field(
|
|
62
|
+
default=0,
|
|
63
|
+
alias="rec_RecTypeID",
|
|
64
|
+
validation_alias="rec_RecTypeID",
|
|
65
|
+
serialization_alias="type_id",
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Convert each of the record's details into a Pydantic kwarg
|
|
71
|
+
for detail in detail_metadata:
|
|
72
|
+
# Add the field's default parsed value
|
|
73
|
+
annotation = PydanticField(**detail)
|
|
74
|
+
field = annotation.build_field()
|
|
75
|
+
kwargs.update(field)
|
|
76
|
+
|
|
77
|
+
if detail["dty_Type"] == "enum":
|
|
78
|
+
field = annotation.build_term_fk()
|
|
79
|
+
kwargs.update(field)
|
|
80
|
+
|
|
81
|
+
# Using Pydantic's 'create_model' module, build the dynamic model
|
|
82
|
+
return pydantic_create_model(model_name, **kwargs)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
|
+
|
|
4
|
+
from heurist.validators import parse_heurist_date
|
|
5
|
+
from pydantic import BaseModel, BeforeValidator, Field
|
|
6
|
+
|
|
7
|
+
PROFILE_MAP = {"0": "flat", "1": "central", "2": "slowStart", "3": "slowFinish"}
|
|
8
|
+
DETERMINATION_MAP = {
|
|
9
|
+
"0": "unknown",
|
|
10
|
+
"1": "attested",
|
|
11
|
+
"2": "conjecture",
|
|
12
|
+
"3": "measurement",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_profile(value) -> str | None:
|
|
17
|
+
if PROFILE_MAP.get(value):
|
|
18
|
+
return PROFILE_MAP[value]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_determination(value) -> str | None:
|
|
22
|
+
if DETERMINATION_MAP.get(value):
|
|
23
|
+
return DETERMINATION_MAP[value]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
HeuristDate = Annotated[datetime, BeforeValidator(parse_heurist_date)]
|
|
27
|
+
HeuristProfile = Annotated[str, BeforeValidator(parse_profile)]
|
|
28
|
+
HeuristDetermination = Annotated[str, BeforeValidator(parse_determination)]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DateLimit(BaseModel):
|
|
32
|
+
earliest: Optional[HeuristDate] = Field(default=None)
|
|
33
|
+
latest: Optional[HeuristDate] = Field(default=None)
|
|
34
|
+
estProfile: Optional[HeuristProfile] = Field(
|
|
35
|
+
default=None, validation_alias="profile"
|
|
36
|
+
)
|
|
37
|
+
estDetermination: Optional[HeuristDetermination] = Field(
|
|
38
|
+
default=None, validation_alias="determination"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Timestamp(BaseModel):
|
|
43
|
+
inYear: Optional[HeuristDate] = Field(default=None, alias="in")
|
|
44
|
+
typeTime: Optional[str] = Field(default=None, alias="type")
|
|
45
|
+
circa: Optional[bool] = Field(default=False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TemporalObject(BaseModel):
|
|
49
|
+
comment: Optional[str] = Field(default=None)
|
|
50
|
+
value: Optional[HeuristDate] = Field(default=None)
|
|
51
|
+
start: Optional[DateLimit] = Field(default=DateLimit(**{}))
|
|
52
|
+
end: Optional[DateLimit] = Field(default=DateLimit(**{}))
|
|
53
|
+
estDetermination: Optional[HeuristDetermination] = Field(
|
|
54
|
+
default=None, validation_alias="determination"
|
|
55
|
+
)
|
|
56
|
+
estProfile: Optional[HeuristProfile] = Field(
|
|
57
|
+
default=None, validation_alias="profile"
|
|
58
|
+
)
|
|
59
|
+
timestamp: Optional[Timestamp] = Field(default=Timestamp(**{}))
|
|
60
|
+
estMinDate: Optional[HeuristDate] = Field(default=None)
|
|
61
|
+
estMaxDate: Optional[HeuristDate] = Field(default=None)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Dataclass to organize and convert the data type of a Record's detail."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FieldType:
|
|
9
|
+
"""Organize and convert the data types of a Record's detail."""
|
|
10
|
+
|
|
11
|
+
dropdown = "enum"
|
|
12
|
+
numeric = "float"
|
|
13
|
+
single_line = "freetext"
|
|
14
|
+
multi_line = "blocktext"
|
|
15
|
+
date_time = "date"
|
|
16
|
+
geospatial = "geo"
|
|
17
|
+
file_or_media_url = "file"
|
|
18
|
+
record_pointer = "resource"
|
|
19
|
+
relationship_marker = "relmarker"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def to_sql(cls, datatype: str) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Convert a Heurist data type label (i.e. "enum") to an SQL equivalent.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
datatype (str): Heurist data type.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: SQL data type.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
if datatype == cls.numeric:
|
|
34
|
+
return "FLOAT"
|
|
35
|
+
elif datatype == cls.date_time:
|
|
36
|
+
return "DATE[2]"
|
|
37
|
+
elif datatype == cls.record_pointer or datatype == cls.relationship_marker:
|
|
38
|
+
return "INTEGER"
|
|
39
|
+
elif datatype == cls.dropdown:
|
|
40
|
+
return "VARCHAR"
|
|
41
|
+
else:
|
|
42
|
+
return "TEXT"
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_detail(cls, detail: dict) -> str:
|
|
46
|
+
"""Extract the field type from a record's detail.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
detail (dict): Record's detail.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
str: Field type name.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
return detail["fieldType"]
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def to_pydantic(cls, datatype: str) -> Any:
|
|
59
|
+
"""Convert Heurist field type to Python type.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
datatype (str): Field type name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Any: Python type.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
if datatype == cls.dropdown:
|
|
69
|
+
return Optional[str]
|
|
70
|
+
|
|
71
|
+
elif datatype == cls.numeric:
|
|
72
|
+
return Optional[float]
|
|
73
|
+
|
|
74
|
+
elif datatype == cls.single_line:
|
|
75
|
+
return Optional[str]
|
|
76
|
+
|
|
77
|
+
elif datatype == cls.multi_line:
|
|
78
|
+
return Optional[str]
|
|
79
|
+
|
|
80
|
+
elif datatype == cls.date_time:
|
|
81
|
+
return dict
|
|
82
|
+
|
|
83
|
+
elif datatype == cls.geospatial:
|
|
84
|
+
return Optional[str]
|
|
85
|
+
|
|
86
|
+
elif datatype == cls.file_or_media_url:
|
|
87
|
+
return Optional[str]
|
|
88
|
+
|
|
89
|
+
elif datatype == cls.record_pointer:
|
|
90
|
+
return Optional[int]
|
|
91
|
+
|
|
92
|
+
elif datatype == cls.relationship_marker:
|
|
93
|
+
return Optional[str]
|
|
94
|
+
|
|
95
|
+
else:
|
|
96
|
+
return Optional[str]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from heurist.models.structural.dty import DTY
|
|
2
|
+
from pydantic_xml import BaseXmlModel, element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DetailTypes(BaseXmlModel):
|
|
6
|
+
"""Dataclass for modeling all of the database structure's Detail Types.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
dty (list): list of instantiated dataclasses that model all of the database's
|
|
10
|
+
Detail Types.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
>>> from mock_data import DB_STRUCTURE_XML
|
|
14
|
+
>>> from heurist.models.structural import HMLStructure
|
|
15
|
+
>>>
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Parse structure
|
|
18
|
+
>>> xml = DB_STRUCTURE_XML
|
|
19
|
+
>>> hml = HMLStructure.from_xml(xml)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Test class
|
|
22
|
+
>>> first_detail_type = hml.DetailTypes.dty[0]
|
|
23
|
+
>>> first_detail_type.dty_ID
|
|
24
|
+
1
|
|
25
|
+
>>> singular_pointer = [d for d in hml.DetailTypes.dty if d.dty_ID == 1295][0]
|
|
26
|
+
>>> singular_pointer.dty_PtrTargetRectypeIDs
|
|
27
|
+
[101]
|
|
28
|
+
>>> plural_pointer = [d for d in hml.DetailTypes.dty if d.dty_ID == 1256][0]
|
|
29
|
+
>>> plural_pointer.dty_PtrTargetRectypeIDs
|
|
30
|
+
[101, 105, 106]
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
dty: list[DTY] = element()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from heurist.models.structural.rst import RST
|
|
2
|
+
from pydantic_xml import BaseXmlModel, element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RecStructure(BaseXmlModel):
|
|
6
|
+
"""Dataclass for modeling all of the database structure's Record Structures.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
rst (list): list of instantiated dataclasses that model all of the database's
|
|
10
|
+
Record Structures.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
>>> from mock_data import DB_STRUCTURE_XML
|
|
14
|
+
>>> from heurist.models.structural import HMLStructure
|
|
15
|
+
>>>
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Parse structure
|
|
18
|
+
>>> xml = DB_STRUCTURE_XML
|
|
19
|
+
>>> hml = HMLStructure.from_xml(xml)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Test class
|
|
22
|
+
>>> first_record_structure = hml.RecStructure.rst[0]
|
|
23
|
+
>>> first_record_structure.rst_ID
|
|
24
|
+
1
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
rst: list[RST] = element()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from heurist.models.structural.rtg import RTG
|
|
2
|
+
from pydantic_xml import BaseXmlModel, element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RecTypeGroups(BaseXmlModel):
|
|
6
|
+
"""Dataclass for modeling all of the database structure's Record Type Groups.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
rtg (list): list of instantiated dataclasses that model all of the database's
|
|
10
|
+
Record Type Groups.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
>>> from mock_data import DB_STRUCTURE_XML
|
|
14
|
+
>>> from heurist.models.structural import HMLStructure
|
|
15
|
+
>>>
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Parse structure
|
|
18
|
+
>>> xml = DB_STRUCTURE_XML
|
|
19
|
+
>>> hml = HMLStructure.from_xml(xml)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Test class
|
|
22
|
+
>>> first_record_type = hml.RecTypeGroups.rtg[0]
|
|
23
|
+
>>> first_record_type.rtg_ID
|
|
24
|
+
4
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
rtg: list[RTG] = element()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from heurist.models.structural.rty import RTY
|
|
2
|
+
from pydantic_xml import BaseXmlModel, element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RecTypes(BaseXmlModel):
|
|
6
|
+
"""Dataclass for modeling all of the database structure's Record Types.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
rty (list): list of instantiated dataclasses that model all of the database's
|
|
10
|
+
Record Types.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
>>> from mock_data import DB_STRUCTURE_XML
|
|
14
|
+
>>> from heurist.models.structural import HMLStructure
|
|
15
|
+
>>>
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Parse structure
|
|
18
|
+
>>> xml = DB_STRUCTURE_XML
|
|
19
|
+
>>> hml = HMLStructure.from_xml(xml)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Test class
|
|
22
|
+
>>> first_record_type = hml.RecTypes.rty[0]
|
|
23
|
+
>>> first_record_type.rty_ID
|
|
24
|
+
1
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
rty: list[RTY] = element()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from heurist.models.structural.trm import TRM
|
|
2
|
+
from pydantic_xml import BaseXmlModel, element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Terms(BaseXmlModel):
|
|
6
|
+
"""Dataclass for modeling all of the database structure's Terms.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
trm (list): list of instantiated dataclasses that model all of the database's
|
|
10
|
+
Terms.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
>>> from mock_data import DB_STRUCTURE_XML
|
|
14
|
+
>>> from heurist.models.structural import HMLStructure
|
|
15
|
+
>>>
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Parse structure
|
|
18
|
+
>>> xml = DB_STRUCTURE_XML
|
|
19
|
+
>>> hml = HMLStructure.from_xml(xml)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Test class
|
|
22
|
+
>>> first_detail_type = hml.Terms.trm[0]
|
|
23
|
+
>>> first_detail_type.trm_ID
|
|
24
|
+
12
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
trm: list[TRM] = element()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from heurist.models.structural.DetailTypes import DetailTypes
|
|
2
|
+
from heurist.models.structural.dty import DTY
|
|
3
|
+
from heurist.models.structural.hml_structure import HMLStructure
|
|
4
|
+
from heurist.models.structural.RecStructure import RecStructure
|
|
5
|
+
from heurist.models.structural.RecTypeGroups import RecTypeGroups
|
|
6
|
+
from heurist.models.structural.RecTypes import RecTypes
|
|
7
|
+
from heurist.models.structural.rst import RST
|
|
8
|
+
from heurist.models.structural.rtg import RTG
|
|
9
|
+
from heurist.models.structural.rty import RTY
|
|
10
|
+
|
|
11
|
+
DetailTypes
|
|
12
|
+
DTY
|
|
13
|
+
HMLStructure
|
|
14
|
+
RecStructure
|
|
15
|
+
RecTypeGroups
|
|
16
|
+
RecTypes
|
|
17
|
+
RST
|
|
18
|
+
RTG
|
|
19
|
+
RTY
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Literal, Optional
|
|
3
|
+
|
|
4
|
+
from heurist.models.structural.utils import split_ids
|
|
5
|
+
from pydantic import field_validator
|
|
6
|
+
from pydantic_xml import BaseXmlModel, element
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DTY(BaseXmlModel, tag="dty", search_mode="unordered"):
|
|
10
|
+
"""Dataclass to model one of the database's Detail Types. A Detail Type is the
|
|
11
|
+
generic schema that defines the type of data one of a record's field.
|
|
12
|
+
|
|
13
|
+
When possible, the attribute descriptions are taken from Heurist's source code.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
dty_ID (int): Code for the detail type (field) - may vary between Heurist DBs.
|
|
17
|
+
dty_Name (str): The canonical (standard) name of the detail type, used as \
|
|
18
|
+
default in edit form.
|
|
19
|
+
dty_Documentation (Optional[str]): Documentation of the detail type, what it \
|
|
20
|
+
means, how defined.
|
|
21
|
+
dty_Type (Literal['freetext','blocktext','integer','date','year','relmarker',\
|
|
22
|
+
'boolean','enum','relationtype','resource','float','file','geo','separator',\
|
|
23
|
+
'calculated','fieldsetmarker','urlinclude']): The value-type of this \
|
|
24
|
+
detail type, what sort of data is stored.
|
|
25
|
+
dty_HelpText (Optional[str]):The default help text displayed to the user under \
|
|
26
|
+
the field.
|
|
27
|
+
dty_ExtendedDescription (Optional[str]): Extended text describing this detail \
|
|
28
|
+
type, for display in rollover.
|
|
29
|
+
dty_EntryMask (Optional[str]): Data entry mask, use to control decimals on \
|
|
30
|
+
numeric values, content of text fields etc.
|
|
31
|
+
dty_Status (Literal["reserved","approved","pending","open"]): 'Reserved' for \
|
|
32
|
+
the system, cannot be changed; 'Approved' for community standards; \
|
|
33
|
+
'Pending' for work in progress; 'Open' for freely modifiable/personal \
|
|
34
|
+
record types.
|
|
35
|
+
dty_OriginatingDBID (int): Database where this detail type originated, 0 = \
|
|
36
|
+
locally.
|
|
37
|
+
dty_NameInOriginatingDB (Optional[str]): Name used in database where this \
|
|
38
|
+
detail type originated.
|
|
39
|
+
dty_IDInOriginatingDB (int): ID used in database where this detail type \
|
|
40
|
+
originated.
|
|
41
|
+
dty_DetailTypeGroupID (int): The general role of this detail allowing \
|
|
42
|
+
differentiated lists of detail types.
|
|
43
|
+
dty_OrderInGroup (int): The display order of DetailType within group, \
|
|
44
|
+
alphabetic if equal values.
|
|
45
|
+
dty_JsonTermIDTree (Optional[str]): Tree of Term IDs to show for this field \
|
|
46
|
+
(display-only header terms set in HeaderTermIDs).
|
|
47
|
+
dty_TermIDTreeNonSelectableIDs (List[Optional[int]]): Term IDs to use as \
|
|
48
|
+
non-selectable headers for this field.
|
|
49
|
+
dty_PtrTargetRectypeIDs (List[Optional[int]]): CSVlist of target Rectype IDs, \
|
|
50
|
+
null = any.
|
|
51
|
+
dty_FieldSetRectypeID (Optional[int]): For a FieldSetMarker, the record type \
|
|
52
|
+
to be inserted as a fieldset.
|
|
53
|
+
dty_ShowInLists (bool): Show this field type in pulldown lists etc. (always \
|
|
54
|
+
visible in field management screen).
|
|
55
|
+
dty_NonOwnerVisibility (Literal["hidden","viewable","public"]): Hidden = \
|
|
56
|
+
visible only to owners, Viewable = any logged in user, Public = visible \
|
|
57
|
+
to non-logged in viewers.
|
|
58
|
+
dty_Modified (datetime): Date of last modification of this record, used to get \
|
|
59
|
+
last updated date for table.
|
|
60
|
+
dty_LocallyModified (bool): Flags a definition element which has been modified \
|
|
61
|
+
relative to the original source.
|
|
62
|
+
dty_SemanticReferenceURL (Optional[str]): URI to a full description or \
|
|
63
|
+
ontological reference definition of the base field (optional).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
dty_ID: int = element()
|
|
67
|
+
dty_Name: str = element()
|
|
68
|
+
dty_Documentation: Optional[str] = element(default=None)
|
|
69
|
+
dty_Type: Literal[
|
|
70
|
+
"freetext",
|
|
71
|
+
"blocktext",
|
|
72
|
+
"integer",
|
|
73
|
+
"date",
|
|
74
|
+
"year",
|
|
75
|
+
"relmarker",
|
|
76
|
+
"boolean",
|
|
77
|
+
"enum",
|
|
78
|
+
"relationtype",
|
|
79
|
+
"resource",
|
|
80
|
+
"float",
|
|
81
|
+
"file",
|
|
82
|
+
"geo",
|
|
83
|
+
"separator",
|
|
84
|
+
"calculated",
|
|
85
|
+
"fieldsetmarker",
|
|
86
|
+
"urlinclude",
|
|
87
|
+
] = element()
|
|
88
|
+
dty_HelpText: Optional[str] = element(default=None)
|
|
89
|
+
dty_ExtendedDescription: Optional[str] = element(default=None)
|
|
90
|
+
dty_EntryMask: Optional[str] = element(default=None)
|
|
91
|
+
dty_Status: Literal["reserved", "approved", "pending", "open"] = element()
|
|
92
|
+
dty_OriginatingDBID: int = element()
|
|
93
|
+
dty_NameInOriginatingDB: Optional[str] = element(default=None)
|
|
94
|
+
dty_IDInOriginatingDB: int = element()
|
|
95
|
+
dty_DetailTypeGroupID: int = element()
|
|
96
|
+
dty_OrderInGroup: int = element()
|
|
97
|
+
dty_JsonTermIDTree: Optional[str] = element(default=None)
|
|
98
|
+
dty_TermIDTreeNonSelectableIDs: List[Optional[int]] = element(default=[])
|
|
99
|
+
dty_PtrTargetRectypeIDs: List[Optional[int]] = element(default=[])
|
|
100
|
+
dty_FieldSetRectypeID: Optional[int] = element(default=None)
|
|
101
|
+
dty_ShowInLists: bool = element()
|
|
102
|
+
dty_NonOwnerVisibility: Literal["hidden", "viewable", "public"] = element()
|
|
103
|
+
dty_Modified: datetime = element()
|
|
104
|
+
dty_LocallyModified: bool = element()
|
|
105
|
+
dty_SemanticReferenceURL: Optional[str] = element(default=None)
|
|
106
|
+
|
|
107
|
+
@field_validator("dty_TermIDTreeNonSelectableIDs", mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def validate_selectable_ids(cls, input_value: str | None) -> list:
|
|
110
|
+
if input_value:
|
|
111
|
+
return split_ids(input=input_value)
|
|
112
|
+
else:
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
@field_validator("dty_PtrTargetRectypeIDs", mode="before")
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_rectype_ids(cls, input_value: str | None) -> list:
|
|
118
|
+
if input_value:
|
|
119
|
+
return split_ids(input=input_value)
|
|
120
|
+
else:
|
|
121
|
+
return []
|