docling-core 0.0.1__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 docling-core might be problematic. Click here for more details.

Files changed (46) hide show
  1. docling_core/__init__.py +6 -0
  2. docling_core/py.typed +0 -0
  3. docling_core/resources/schemas/doc/ANN.json +171 -0
  4. docling_core/resources/schemas/doc/DOC.json +300 -0
  5. docling_core/resources/schemas/doc/OCR-output.json +166 -0
  6. docling_core/resources/schemas/doc/RAW.json +158 -0
  7. docling_core/resources/schemas/generated/ccs_document_schema.json +1071 -0
  8. docling_core/resources/schemas/generated/minimal_document_schema_flat.json +1129 -0
  9. docling_core/resources/schemas/search/search_doc_mapping.json +104 -0
  10. docling_core/resources/schemas/search/search_doc_mapping_v2.json +256 -0
  11. docling_core/search/__init__.py +6 -0
  12. docling_core/search/json_schema_to_search_mapper.py +406 -0
  13. docling_core/search/mapping.py +29 -0
  14. docling_core/search/meta.py +93 -0
  15. docling_core/search/package.py +56 -0
  16. docling_core/types/__init__.py +25 -0
  17. docling_core/types/base.py +248 -0
  18. docling_core/types/doc/__init__.py +6 -0
  19. docling_core/types/doc/base.py +199 -0
  20. docling_core/types/doc/doc_ann.py +76 -0
  21. docling_core/types/doc/doc_ocr.py +83 -0
  22. docling_core/types/doc/doc_raw.py +187 -0
  23. docling_core/types/doc/document.py +393 -0
  24. docling_core/types/gen/__init__.py +6 -0
  25. docling_core/types/gen/generic.py +33 -0
  26. docling_core/types/nlp/__init__.py +6 -0
  27. docling_core/types/nlp/qa.py +74 -0
  28. docling_core/types/nlp/qa_labels.py +118 -0
  29. docling_core/types/rec/__init__.py +6 -0
  30. docling_core/types/rec/attribute.py +55 -0
  31. docling_core/types/rec/base.py +90 -0
  32. docling_core/types/rec/predicate.py +133 -0
  33. docling_core/types/rec/record.py +95 -0
  34. docling_core/types/rec/statement.py +41 -0
  35. docling_core/types/rec/subject.py +77 -0
  36. docling_core/utils/__init__.py +6 -0
  37. docling_core/utils/alias.py +27 -0
  38. docling_core/utils/ds_generate_docs.py +144 -0
  39. docling_core/utils/ds_generate_jsonschema.py +62 -0
  40. docling_core/utils/validate.py +86 -0
  41. docling_core/utils/validators.py +100 -0
  42. docling_core-0.0.1.dist-info/LICENSE +21 -0
  43. docling_core-0.0.1.dist-info/METADATA +133 -0
  44. docling_core-0.0.1.dist-info/RECORD +46 -0
  45. docling_core-0.0.1.dist-info/WHEEL +4 -0
  46. docling_core-0.0.1.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,406 @@
1
+ #
2
+ # Copyright IBM Corp. 2024 - 2024
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ """Methods to convert a JSON Schema into a search database schema."""
7
+ import re
8
+ from copy import deepcopy
9
+ from typing import Any, Optional, Pattern, Tuple, TypedDict
10
+
11
+ from jsonref import JsonRef
12
+
13
+
14
+ class SearchIndexDefinition(TypedDict):
15
+ """Data type for an index basic definition (settings and mappings)."""
16
+
17
+ settings: dict
18
+ mappings: dict
19
+
20
+
21
+ class JsonSchemaToSearchMapper:
22
+ """Map a JSON Schema to an search database schema.
23
+
24
+ The generated database schema is a mapping describing the fields from the
25
+ JSON Schema and how they should be indexed in a Lucene index of a search database.
26
+
27
+ Potential issues:
28
+ - Tuples may not be converted properly (e.g., Tuple[float,float,float,str,str])
29
+ - Method `_remove_keys` may lead to wrong results if a field is named `properties`.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ settings_extra: Optional[dict] = None,
35
+ mappings_extra: Optional[dict] = None,
36
+ ):
37
+ """Create an instance of the mapper with default settings."""
38
+ self.settings = {
39
+ "analysis": {
40
+ # Create a normalizer for lowercase ascii folding,
41
+ # this is used in keyword fields
42
+ "normalizer": {
43
+ "lowercase_asciifolding": {
44
+ "type": "custom",
45
+ "filter": ["lowercase", "asciifolding"],
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ self.settings_extra = settings_extra
52
+ self.mappings_extra = mappings_extra
53
+
54
+ self._re_es_flag = re.compile(r"^(?:x-es-)(.*)")
55
+
56
+ self._rm_keys = (
57
+ "description",
58
+ "required",
59
+ "title",
60
+ "additionalProperties",
61
+ "format",
62
+ "enum",
63
+ "pattern",
64
+ "$comment",
65
+ "default",
66
+ "minItems",
67
+ "maxItems",
68
+ "minimum",
69
+ "maximum",
70
+ "minLength",
71
+ "maxLength",
72
+ "exclusiveMinimum",
73
+ "exclusiveMaximum",
74
+ "$defs",
75
+ "const",
76
+ )
77
+
78
+ self._suppress_key = "x-es-suppress"
79
+
80
+ self._type_format_mappings: dict[tuple[str, str], str] = {
81
+ ("string", "date-time"): "date",
82
+ }
83
+
84
+ self._type_mappings = {
85
+ "number": "double",
86
+ "string": "text",
87
+ }
88
+
89
+ self._types_to_remove = ("object",)
90
+
91
+ def get_index_definition(self, schema: dict) -> SearchIndexDefinition:
92
+ """Generates a search database schema from a JSON Schema.
93
+
94
+ The search database schema consists of the sections `settings` and `mappings`,
95
+ which define the fields, their data types, and other specifications to index
96
+ JSON documents into a Lucene index.
97
+ """
98
+ mapping = JsonRef.replace_refs(schema)
99
+
100
+ mapping = self._merge_unions(mapping)
101
+
102
+ mapping = self._clean_types(mapping)
103
+
104
+ mapping = self._collapse_arrays(mapping)
105
+
106
+ mapping = self._remove_keys(mapping, self._rm_keys)
107
+
108
+ mapping = self._suppress(mapping, self._suppress_key)
109
+
110
+ mapping = self._translate_keys_re(mapping)
111
+
112
+ mapping = self._clean(mapping)
113
+
114
+ mapping.pop("definitions", None)
115
+
116
+ result = SearchIndexDefinition(
117
+ settings=self.settings,
118
+ mappings=mapping,
119
+ )
120
+
121
+ if self.mappings_extra:
122
+ result["mappings"] = {**result["mappings"], **self.mappings_extra}
123
+
124
+ if self.settings_extra:
125
+ result["settings"] = {**result["settings"], **self.settings_extra}
126
+
127
+ return result
128
+
129
+ def _merge_unions(self, doc: dict) -> dict:
130
+ """Merge objects of type anyOf, allOf, or oneOf (options).
131
+
132
+ Args:
133
+ doc: A JSON schema or a transformation towards a search database mappings.
134
+
135
+ Returns:
136
+ A transformation of a JSON schema by merging option fields.
137
+ """
138
+
139
+ def _clean(value: Any) -> Any:
140
+ if isinstance(value, list):
141
+ return [_clean(v) for v in value]
142
+
143
+ if isinstance(value, dict):
144
+ union: list = []
145
+ merged_union: dict = {}
146
+
147
+ for k, v in value.items():
148
+ if k in ("oneOf", "allOf", "anyOf"):
149
+ union.extend(v)
150
+ else:
151
+ merged_union[k] = v
152
+
153
+ if not union:
154
+ return {k: _clean(v) for k, v in value.items()}
155
+
156
+ for u in union:
157
+ if not isinstance(u, dict):
158
+ continue
159
+
160
+ for k, v in u.items():
161
+ if k == "type" and v == "null": # null values are irrelevant
162
+ continue
163
+ elif not isinstance(v, dict) or k not in merged_union:
164
+ merged_union[k] = _clean(v)
165
+ elif isinstance(v, dict) and k in merged_union:
166
+ merged_union[k] = _clean({**merged_union[k], **v})
167
+
168
+ return merged_union
169
+
170
+ return value
171
+
172
+ return _clean(doc)
173
+
174
+ def _clean_types(self, doc: dict) -> dict:
175
+ """Clean field types originated from a JSON schema to obtain search mappings.
176
+
177
+ Args:
178
+ doc: A JSON schema or a transformation towards a search database mappings.
179
+
180
+ Returns:
181
+ A transformation of a JSON schema by merging option fields.
182
+ """
183
+
184
+ def _clean(value: Any) -> Any:
185
+ if isinstance(value, list):
186
+ return [_clean(v) for v in value]
187
+
188
+ if isinstance(value, dict):
189
+ if isinstance(value.get("type"), str):
190
+ t: str = value["type"]
191
+
192
+ # Tuples
193
+ if t == "array" and isinstance(value.get("items"), list):
194
+ items: list = value["items"]
195
+
196
+ if items:
197
+ value["items"] = value["items"][0]
198
+ else:
199
+ value["items"] = {}
200
+
201
+ # Unwanted types, such as 'object'
202
+ if t in self._types_to_remove:
203
+ value.pop("type", None)
204
+
205
+ # Map formats
206
+ f: str = value.get("format", "")
207
+ if (t, f) in self._type_format_mappings:
208
+ value["type"] = self._type_format_mappings[(t, f)]
209
+ value.pop("format", None)
210
+
211
+ # Map types, such as 'string' to 'text'
212
+ elif t in self._type_mappings:
213
+ value["type"] = self._type_mappings[t]
214
+
215
+ return {k: _clean(v) for k, v in value.items()}
216
+
217
+ return value
218
+
219
+ return _clean(doc)
220
+
221
+ @staticmethod
222
+ def _collapse_arrays(doc: dict) -> dict:
223
+ """Collapse arrays from a JSON schema to match a search database mappings.
224
+
225
+ Args:
226
+ doc: A JSON schema or a transformation towards a search database mappings.
227
+
228
+ Returns:
229
+ A transformation of a JSON schema by collapsing arrays.
230
+ """
231
+
232
+ def __collapse(d_: Any) -> Any:
233
+ if isinstance(d_, list):
234
+ return [v for v in (__collapse(v) for v in d_)]
235
+
236
+ if isinstance(d_, dict):
237
+ if "type" in d_ and d_["type"] == "array" and "items" in d_:
238
+ collapsed = __collapse(d_["items"])
239
+
240
+ d_ = deepcopy(d_)
241
+ d_.pop("items", None)
242
+ d_.pop("type", None)
243
+
244
+ merged = {**d_, **collapsed}
245
+
246
+ return merged
247
+
248
+ return {k: __collapse(v) for k, v in d_.items()}
249
+
250
+ return d_
251
+
252
+ return __collapse(doc)
253
+
254
+ @staticmethod
255
+ def _suppress(doc: dict, suppress_key: str) -> dict:
256
+ """Remove a key from a JSON schema to match a search database mappings.
257
+
258
+ Args:
259
+ doc: A JSON schema or a transformation towards a search database mappings.
260
+ key: The name of a field to be removed from the `doc`.
261
+
262
+ Returns:
263
+ A transformation of a JSON schema by removing the field `suppress_key`.
264
+ """
265
+
266
+ def __suppress(d_: Any) -> Any:
267
+ if isinstance(d_, list):
268
+ return [v for v in (__suppress(v) for v in d_)]
269
+
270
+ if isinstance(d_, dict):
271
+ if suppress_key in d_ and d_[suppress_key] is True:
272
+ return {}
273
+ else:
274
+ return {
275
+ k: v for k, v in ((k, __suppress(v)) for k, v in d_.items())
276
+ }
277
+ return d_
278
+
279
+ return __suppress(doc)
280
+
281
+ @staticmethod
282
+ def _remove_keys(doc: dict, keys: Tuple[str, ...]) -> dict:
283
+ """Remove keys from a JSON schema to match a search database mappings.
284
+
285
+ Args:
286
+ doc: A JSON schema or a transformation towards a search database mappings.
287
+ keys: Fields to be removed from the `doc`.
288
+
289
+ Returns:
290
+ A transformation of a JSON schema by removing the fields in `keys`.
291
+ """
292
+
293
+ def __remove(d_: Any) -> Any:
294
+ if isinstance(d_, list):
295
+ return [v for v in (__remove(v) for v in d_)]
296
+
297
+ if isinstance(d_, dict):
298
+ result = {}
299
+ for k, v in d_.items():
300
+ if k == "properties" and isinstance(v, dict):
301
+ # All properties must be included, they are not to be removed,
302
+ # even if they have a name of a key that's to be removed.
303
+ result[k] = {p_k: __remove(p_v) for p_k, p_v in v.items()}
304
+ elif k not in keys:
305
+ result[k] = __remove(v)
306
+
307
+ return result
308
+
309
+ return d_
310
+
311
+ return __remove(doc)
312
+
313
+ @staticmethod
314
+ def _remove_keys_re(doc: dict, regx: Pattern) -> dict:
315
+ """Remove keys from a JSON schema to match a search database mappings.
316
+
317
+ Args:
318
+ doc: A JSON schema or a transformation towards a search database mappings.
319
+ keys: A pattern defining the fields to be removed from the `doc`.
320
+
321
+ Returns:
322
+ A transformation of a JSON schema by removing fields with a name pattern.
323
+ """
324
+
325
+ def __remove(d_: Any) -> Any:
326
+ if isinstance(d_, list):
327
+ return [v for v in (__remove(v) for v in d_)]
328
+
329
+ if isinstance(d_, dict):
330
+ return {
331
+ k: v
332
+ for k, v in (
333
+ (k, __remove(v)) for k, v in d_.items() if not regx.match(k)
334
+ )
335
+ }
336
+
337
+ return d_
338
+
339
+ return __remove(doc)
340
+
341
+ def _translate_keys_re(self, doc: dict) -> dict:
342
+ """Translate marked keys from a JSON schema to match a search database mappings.
343
+
344
+ The keys to be translated should have a name that matches the pattern defined
345
+ by this class patter, for instance, a name starting with `x-es-`.
346
+
347
+ Args:
348
+ doc: A JSON schema or a transformation towards a search database mappings.
349
+
350
+ Returns:
351
+ A transformation of a JSON schema towards a search database mappings.
352
+ """
353
+
354
+ def __translate(d_: Any) -> Any:
355
+ if isinstance(d_, list):
356
+ return [v for v in (__translate(v) for v in d_)]
357
+
358
+ if isinstance(d_, dict):
359
+ new_dict = {}
360
+ for k, v in d_.items():
361
+ new_dict[k] = __translate(v)
362
+
363
+ delkeys = []
364
+ for k in list(new_dict.keys()):
365
+ k_ = self._re_es_flag.sub(r"\1", k)
366
+ if k_ != k:
367
+ new_dict[k_] = new_dict[k]
368
+ delkeys.append(k)
369
+
370
+ for k in delkeys:
371
+ new_dict.pop(k, None)
372
+
373
+ return new_dict
374
+
375
+ return d_
376
+
377
+ return __translate(doc)
378
+
379
+ @staticmethod
380
+ def _clean(doc: dict) -> dict:
381
+ """Recursively remove empty lists, dicts, strings, or None elements from a dict.
382
+
383
+ Args:
384
+ doc: A JSON schema or a transformation towards a search database mappings.
385
+
386
+ Returns:
387
+ A transformation of a JSON schema by removing empty objects.
388
+ """
389
+
390
+ def _empty(x) -> bool:
391
+ return x is None or x == {} or x == [] or x == ""
392
+
393
+ def _clean(d_: Any) -> Any:
394
+ if isinstance(d_, list):
395
+ return [v for v in (_clean(v) for v in d_) if not _empty(v)]
396
+
397
+ if isinstance(d_, dict):
398
+ return {
399
+ k: v
400
+ for k, v in ((k, _clean(v)) for k, v in d_.items())
401
+ if not _empty(v)
402
+ }
403
+
404
+ return d_
405
+
406
+ return _clean(doc)
@@ -0,0 +1,29 @@
1
+ #
2
+ # Copyright IBM Corp. 2024 - 2024
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ """Methods to define fields in an index mapping of a search database."""
7
+ from typing import Any, Optional
8
+
9
+
10
+ def es_field(
11
+ *,
12
+ type: Optional[str] = None,
13
+ ignore_above: Optional[int] = None,
14
+ term_vector: Optional[str] = None,
15
+ **kwargs: Any,
16
+ ):
17
+ """Create x-es kwargs to be passed to a `pydantic.Field` via unpacking."""
18
+ all_kwargs = {**kwargs}
19
+
20
+ if type is not None:
21
+ all_kwargs["type"] = type
22
+
23
+ if ignore_above is not None:
24
+ all_kwargs["ignore_above"] = ignore_above
25
+
26
+ if term_vector is not None:
27
+ all_kwargs["term_vector"] = term_vector
28
+
29
+ return {f"x-es-{k}": v for k, v in all_kwargs.items()}
@@ -0,0 +1,93 @@
1
+ #
2
+ # Copyright IBM Corp. 2024 - 2024
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ """Models and methods to define the metadata fields in database index mappings."""
7
+ from pathlib import Path
8
+ from typing import Generic, Optional, TypeVar
9
+
10
+ from pydantic import BaseModel, Field, StrictStr, ValidationInfo, field_validator
11
+
12
+ from docling_core.search.package import Package
13
+ from docling_core.types.base import CollectionTypeEnum, StrictDateTime, UniqueList
14
+ from docling_core.utils.alias import AliasModel
15
+
16
+ ClassificationT = TypeVar("ClassificationT", bound=str)
17
+ DomainT = TypeVar("DomainT", bound=str)
18
+
19
+
20
+ class S3Path(BaseModel, extra="forbid"):
21
+ """The path details within a cloud object storage for CCS-parsed files."""
22
+
23
+ bucket: StrictStr
24
+ prefix: StrictStr
25
+ infix: StrictStr
26
+
27
+ def __hash__(self):
28
+ """Return the hash value for this S3Path object."""
29
+ return hash((type(self),) + tuple(self.__dict__.values()))
30
+
31
+
32
+ class S3CcsData(BaseModel, extra="forbid"):
33
+ """The access details to a cloud object storage for CCS-parsed files."""
34
+
35
+ endpoint: StrictStr
36
+ paths: UniqueList[S3Path] = Field(min_length=1)
37
+
38
+
39
+ class DocumentLicense(BaseModel, extra="forbid"):
40
+ """Document license for a search database index within the index mappings."""
41
+
42
+ code: Optional[list[StrictStr]] = None
43
+ text: Optional[list[StrictStr]] = None
44
+
45
+
46
+ class Meta(AliasModel, Generic[ClassificationT, DomainT], extra="forbid"):
47
+ """Metadata of a search database index within the index mappings."""
48
+
49
+ aliases: Optional[list[StrictStr]] = None
50
+ created: StrictDateTime
51
+ description: Optional[StrictStr] = None
52
+ source: StrictStr
53
+ storage: Optional[StrictStr] = None
54
+ display_name: Optional[StrictStr] = None
55
+ type: CollectionTypeEnum
56
+ classification: Optional[list[ClassificationT]] = None
57
+ version: UniqueList[Package] = Field(min_length=1)
58
+ license: Optional[StrictStr] = None
59
+ filename: Optional[Path] = None
60
+ domain: Optional[list[DomainT]] = None
61
+ reference: Optional[StrictStr] = Field(default=None, alias="$ref")
62
+ ccs_s3_data: Optional[S3CcsData] = None
63
+ document_license: Optional[DocumentLicense] = None
64
+ index_key: Optional[StrictStr] = None
65
+ project_key: Optional[StrictStr] = None
66
+
67
+ @field_validator("reference")
68
+ @classmethod
69
+ def reference_for_document(cls, v, info: ValidationInfo):
70
+ """Validate the reference field for indexes of type Document."""
71
+ if "type" in info.data and info.data["type"] == "Document":
72
+ if v and v != "ccs:schemas#/Document":
73
+ raise ValueError("wrong reference value for Document type")
74
+ else:
75
+ return "ccs:schemas#/Document"
76
+ else:
77
+ return v
78
+
79
+ @field_validator("version")
80
+ @classmethod
81
+ def version_has_schema(cls, v):
82
+ """Validate that the docling-core library is always set in version field."""
83
+ docling_core = [item for item in v if item.name == "docling-core"]
84
+ if not docling_core:
85
+ raise ValueError(
86
+ "the version should include at least a valid docling-core package"
87
+ )
88
+ elif len(docling_core) > 1:
89
+ raise ValueError(
90
+ "the version must not include more than 1 docling-core package"
91
+ )
92
+ else:
93
+ return v
@@ -0,0 +1,56 @@
1
+ #
2
+ # Copyright IBM Corp. 2024 - 2024
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ """Models and methods to define a package model."""
7
+
8
+ import importlib.metadata
9
+ import re
10
+ from typing import Final
11
+
12
+ from pydantic import BaseModel, StrictStr, StringConstraints
13
+ from typing_extensions import Annotated
14
+
15
+ VERSION_PATTERN: Final = (
16
+ r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)"
17
+ r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
18
+ r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+"
19
+ r"(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
20
+ )
21
+
22
+
23
+ class Package(BaseModel, extra="forbid"):
24
+ """Representation of a software package.
25
+
26
+ The version needs to comply with Semantic Versioning 2.0.0.
27
+ """
28
+
29
+ name: StrictStr
30
+ version: Annotated[str, StringConstraints(strict=True, pattern=VERSION_PATTERN)] = (
31
+ importlib.metadata.version("docling-core")
32
+ )
33
+
34
+ def __hash__(self):
35
+ """Return the hash value for this S3Path object."""
36
+ return hash((type(self),) + tuple(self.__dict__.values()))
37
+
38
+ def get_major(self):
39
+ """Get the major version of this package."""
40
+ return re.match(VERSION_PATTERN, self.version)["major"]
41
+
42
+ def get_minor(self):
43
+ """Get the major version of this package."""
44
+ return re.match(VERSION_PATTERN, self.version)["minor"]
45
+
46
+ def get_patch(self):
47
+ """Get the major version of this package."""
48
+ return re.match(VERSION_PATTERN, self.version)["patch"]
49
+
50
+ def get_pre_release(self):
51
+ """Get the pre-release version of this package."""
52
+ return re.match(VERSION_PATTERN, self.version)["prerelease"]
53
+
54
+ def get_build_metadata(self):
55
+ """Get the build metadata version of this package."""
56
+ return re.match(VERSION_PATTERN, self.version)["buildmetadata"]
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright IBM Corp. 2024 - 2024
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ """Define the main types."""
7
+
8
+ from docling_core.types.doc.base import BoundingBox # noqa
9
+ from docling_core.types.doc.base import Table # noqa
10
+ from docling_core.types.doc.base import TableCell # noqa
11
+ from docling_core.types.doc.base import ( # noqa
12
+ BaseCell,
13
+ BaseText,
14
+ PageDimensions,
15
+ PageReference,
16
+ Prov,
17
+ Ref,
18
+ )
19
+ from docling_core.types.doc.document import ( # noqa
20
+ CCSDocumentDescription as DocumentDescription,
21
+ )
22
+ from docling_core.types.doc.document import CCSFileInfoObject as FileInfoObject # noqa
23
+ from docling_core.types.doc.document import ExportedCCSDocument as Document # noqa
24
+ from docling_core.types.gen.generic import Generic # noqa
25
+ from docling_core.types.rec.record import Record # noqa