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.

Files changed (80) hide show
  1. heurist/__init__.py +1 -0
  2. heurist/api/__init__.py +0 -0
  3. heurist/api/client.py +122 -0
  4. heurist/api/connection.py +71 -0
  5. heurist/api/constants.py +19 -0
  6. heurist/api/credentials.py +71 -0
  7. heurist/api/exceptions.py +45 -0
  8. heurist/api/url_builder.py +148 -0
  9. heurist/api/utils.py +24 -0
  10. heurist/cli/__init__.py +0 -0
  11. heurist/cli/__main__.py +227 -0
  12. heurist/cli/load.py +55 -0
  13. heurist/cli/records.py +49 -0
  14. heurist/cli/schema.py +94 -0
  15. heurist/database/__init__.py +3 -0
  16. heurist/database/basedb.py +125 -0
  17. heurist/database/database.py +96 -0
  18. heurist/models/__init__.py +0 -0
  19. heurist/models/dynamic/__init__.py +3 -0
  20. heurist/models/dynamic/annotation.py +143 -0
  21. heurist/models/dynamic/create_model.py +82 -0
  22. heurist/models/dynamic/date.py +61 -0
  23. heurist/models/dynamic/type.py +96 -0
  24. heurist/models/structural/DetailTypes.py +34 -0
  25. heurist/models/structural/RecStructure.py +27 -0
  26. heurist/models/structural/RecTypeGroups.py +27 -0
  27. heurist/models/structural/RecTypes.py +27 -0
  28. heurist/models/structural/Terms.py +27 -0
  29. heurist/models/structural/__init__.py +19 -0
  30. heurist/models/structural/dty.py +121 -0
  31. heurist/models/structural/hml_structure.py +36 -0
  32. heurist/models/structural/rst.py +141 -0
  33. heurist/models/structural/rtg.py +25 -0
  34. heurist/models/structural/rty.py +81 -0
  35. heurist/models/structural/trm.py +34 -0
  36. heurist/models/structural/utils.py +53 -0
  37. heurist/schema/__init__.py +27 -0
  38. heurist/schema/models.py +70 -0
  39. heurist/schema/rel_to_dict.py +39 -0
  40. heurist/sql/__init__.py +21 -0
  41. heurist/sql/joinRecordTypeIDNameByGroupType.sql +10 -0
  42. heurist/sql/joinRecordTypeMetadata.sql +17 -0
  43. heurist/sql/selectRecordTypeSchema.sql +51 -0
  44. heurist/sql/sql_safety.py +101 -0
  45. heurist/utils/constants.py +1 -0
  46. heurist/utils/rel_to_dict_array.py +8 -0
  47. heurist/validators/__init__.py +3 -0
  48. heurist/validators/detail_validator.py +142 -0
  49. heurist/validators/exceptions.py +34 -0
  50. heurist/validators/parse_heurist_date.py +71 -0
  51. heurist/validators/record_validator.py +156 -0
  52. heurist/workflows/__init__.py +3 -0
  53. heurist/workflows/etl.py +66 -0
  54. heurist_api-0.1.2.dist-info/METADATA +453 -0
  55. heurist_api-0.1.2.dist-info/RECORD +80 -0
  56. heurist_api-0.1.2.dist-info/WHEEL +4 -0
  57. heurist_api-0.1.2.dist-info/entry_points.txt +2 -0
  58. heurist_api-0.1.2.dist-info/licenses/LICENSE +427 -0
  59. mock_data/__init__.py +22 -0
  60. mock_data/blocktext/__init__.py +0 -0
  61. mock_data/blocktext/single.py +7 -0
  62. mock_data/date/__init__.py +0 -0
  63. mock_data/date/compound_repeated.py +44 -0
  64. mock_data/date/compound_single.py +30 -0
  65. mock_data/date/simple_single.py +16 -0
  66. mock_data/date/timestamp_repeated.py +30 -0
  67. mock_data/enum/__init__.py +0 -0
  68. mock_data/enum/repeated.py +29 -0
  69. mock_data/enum/single.py +18 -0
  70. mock_data/file/__init__.py +0 -0
  71. mock_data/file/single.py +28 -0
  72. mock_data/float/__init__.py +0 -0
  73. mock_data/float/single.py +8 -0
  74. mock_data/freetext/__init__.py +0 -0
  75. mock_data/freetext/single.py +16 -0
  76. mock_data/geo/__init__.py +0 -0
  77. mock_data/geo/single.py +22 -0
  78. mock_data/resource/__init__.py +0 -0
  79. mock_data/resource/repeated.py +35 -0
  80. 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 []