python-datamodel 0.10.1__cp313-cp313-win32.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.
Files changed (78) hide show
  1. datamodel/__init__.py +13 -0
  2. datamodel/abstract.py +383 -0
  3. datamodel/adaptive/__init__.py +0 -0
  4. datamodel/adaptive/models.py +598 -0
  5. datamodel/aliases/__init__.py +26 -0
  6. datamodel/base.py +180 -0
  7. datamodel/converters.c +43471 -0
  8. datamodel/converters.cp313-win32.pyd +0 -0
  9. datamodel/converters.html +17387 -0
  10. datamodel/converters.pyx +1489 -0
  11. datamodel/exceptions.c +13455 -0
  12. datamodel/exceptions.cp313-win32.pyd +0 -0
  13. datamodel/exceptions.html +1261 -0
  14. datamodel/exceptions.pxd +13 -0
  15. datamodel/exceptions.pyx +50 -0
  16. datamodel/fields.cp313-win32.pyd +0 -0
  17. datamodel/fields.cpp +17401 -0
  18. datamodel/fields.html +3912 -0
  19. datamodel/fields.pyx +309 -0
  20. datamodel/functions.cp313-win32.pyd +0 -0
  21. datamodel/functions.cpp +9068 -0
  22. datamodel/functions.html +1766 -0
  23. datamodel/functions.pxd +9 -0
  24. datamodel/functions.pyx +82 -0
  25. datamodel/jsonld/__init__.py +45 -0
  26. datamodel/jsonld/models.py +500 -0
  27. datamodel/libs/__init__.py +1 -0
  28. datamodel/libs/mapping.c +15067 -0
  29. datamodel/libs/mapping.cp313-win32.pyd +0 -0
  30. datamodel/libs/mapping.html +2618 -0
  31. datamodel/libs/mapping.pxd +11 -0
  32. datamodel/libs/mapping.pyx +135 -0
  33. datamodel/libs/mutables.py +127 -0
  34. datamodel/models.py +814 -0
  35. datamodel/parsers/__init__.py +0 -0
  36. datamodel/parsers/encoders.py +15 -0
  37. datamodel/parsers/json.cp313-win32.pyd +0 -0
  38. datamodel/parsers/json.cpp +17004 -0
  39. datamodel/parsers/json.html +3365 -0
  40. datamodel/parsers/json.pyx +250 -0
  41. datamodel/profiler.py +21 -0
  42. datamodel/py.typed +0 -0
  43. datamodel/rs_core/Cargo.toml +17 -0
  44. datamodel/rs_core/src/lib.rs +294 -0
  45. datamodel/rs_parsers/Cargo.toml +22 -0
  46. datamodel/rs_parsers/src/lib.rs +571 -0
  47. datamodel/rs_parsers.cp313-win32.pyd +0 -0
  48. datamodel/rs_validators/Cargo.toml +17 -0
  49. datamodel/rs_validators/src/lib.rs +0 -0
  50. datamodel/typedefs/__init__.py +9 -0
  51. datamodel/typedefs/singleton.c +9169 -0
  52. datamodel/typedefs/singleton.cp313-win32.pyd +0 -0
  53. datamodel/typedefs/singleton.html +629 -0
  54. datamodel/typedefs/singleton.pxd +9 -0
  55. datamodel/typedefs/singleton.pyx +24 -0
  56. datamodel/typedefs/types.c +11716 -0
  57. datamodel/typedefs/types.cp313-win32.pyd +0 -0
  58. datamodel/typedefs/types.html +732 -0
  59. datamodel/typedefs/types.pxd +11 -0
  60. datamodel/typedefs/types.pyx +39 -0
  61. datamodel/types.c +7165 -0
  62. datamodel/types.cp313-win32.pyd +0 -0
  63. datamodel/types.html +716 -0
  64. datamodel/types.pyx +100 -0
  65. datamodel/validation.cp313-win32.pyd +0 -0
  66. datamodel/validation.cpp +17085 -0
  67. datamodel/validation.html +4769 -0
  68. datamodel/validation.pyx +315 -0
  69. datamodel/version.py +13 -0
  70. examples/nn/examples.py +311 -0
  71. examples/nn/stores.py +151 -0
  72. examples/tests/sp_types.py +294 -0
  73. examples/tests/speed_dates.py +26 -0
  74. python_datamodel-0.10.1.dist-info/LICENSE +29 -0
  75. python_datamodel-0.10.1.dist-info/METADATA +320 -0
  76. python_datamodel-0.10.1.dist-info/RECORD +78 -0
  77. python_datamodel-0.10.1.dist-info/WHEEL +5 -0
  78. python_datamodel-0.10.1.dist-info/top_level.txt +7 -0
@@ -0,0 +1,9 @@
1
+ # function.pxd
2
+ from libcpp cimport bool as bool_t
3
+
4
+ cpdef bool_t is_iterable(object value)
5
+ cpdef bool_t is_primitive(object value)
6
+ cpdef bool_t is_dataclass(object obj)
7
+ cpdef bool_t is_function(object value)
8
+ cpdef bool_t is_callable(object value)
9
+ cpdef bool_t is_empty(object value)
@@ -0,0 +1,82 @@
1
+ # cython: language_level=3, embedsignature=True, boundscheck=False, wraparound=True, initializedcheck=False
2
+ # Copyright (C) 2018-present Jesus Lara
3
+ #
4
+ from typing import get_args, get_origin, Union, Optional
5
+ from collections.abc import Iterable
6
+ from libcpp cimport bool as bool_t
7
+ from cpython.object cimport (
8
+ PyObject_IsInstance,
9
+ PyObject_IsSubclass,
10
+ PyObject_HasAttr,
11
+ )
12
+ from uuid import UUID
13
+ import asyncpg.pgproto.pgproto as pgproto
14
+ from decimal import Decimal
15
+ import datetime
16
+ import types
17
+ from functools import partial
18
+ from dataclasses import _MISSING_TYPE
19
+
20
+
21
+ cpdef bool_t is_iterable(object value):
22
+ """Returns True if value is an iterable."""
23
+ if isinstance(value, Iterable) and not isinstance(value, (str, bytes)):
24
+ return True
25
+ return False
26
+
27
+
28
+ cpdef bool_t is_primitive(object value):
29
+ """Returns True if value is a primitive type."""
30
+ return value in (
31
+ int,
32
+ float,
33
+ str,
34
+ UUID,
35
+ pgproto.UUID,
36
+ Decimal,
37
+ bool,
38
+ bytes,
39
+ datetime.date,
40
+ datetime.datetime,
41
+ datetime.time,
42
+ datetime.timedelta
43
+ )
44
+
45
+
46
+ cpdef bool_t is_dataclass(object obj):
47
+ """Returns True if obj is a dataclass or an instance of a
48
+ dataclass."""
49
+ cls = obj if isinstance(obj, type) and not isinstance(obj, types.GenericAlias) else type(obj)
50
+ return PyObject_HasAttr(cls, '__dataclass_fields__')
51
+
52
+
53
+ cpdef bool_t is_function(object value):
54
+ """Returns True if value is a function."""
55
+ return isinstance(value, (types.BuiltinFunctionType, types.FunctionType, partial))
56
+
57
+
58
+ cpdef bool_t is_callable(object value):
59
+ """Returns True if value is a callable object."""
60
+ if value is None or value == _MISSING_TYPE:
61
+ return False
62
+ if is_function(value):
63
+ return callable(value)
64
+ return False
65
+
66
+ cpdef bool_t is_empty(object value):
67
+ cdef bool_t result = False
68
+ if value is None:
69
+ return True
70
+ if isinstance(value, _MISSING_TYPE) or value == _MISSING_TYPE:
71
+ result = True
72
+ elif isinstance(value, str) and value == '':
73
+ result = True
74
+ elif isinstance(value, (int, float)) and value == 0:
75
+ result = False
76
+ elif isinstance(value, dict) and value == {}:
77
+ result = False
78
+ elif isinstance(value, (list, tuple, set)) and value == []:
79
+ result = False
80
+ elif not value:
81
+ result = True
82
+ return result
@@ -0,0 +1,45 @@
1
+ from .models import (
2
+ URL,
3
+ JobTitle,
4
+ PostalAddress,
5
+ GeoCoordinates,
6
+ Person,
7
+ ImageObject,
8
+ Recipe,
9
+ NutritionInformation,
10
+ Organization,
11
+ AggregateRating,
12
+ Rating,
13
+ Product,
14
+ Review,
15
+ VideoObject,
16
+ AdministrativeArea,
17
+ Audience,
18
+ Place,
19
+ QuantitativeValue,
20
+ MonetaryAmount,
21
+ JobPosting,
22
+ )
23
+
24
+ JSON_MODEL_MAP = {
25
+ "URL": URL,
26
+ "JobTitle": JobTitle,
27
+ "PostalAddress": PostalAddress,
28
+ "GeoCoordinates": GeoCoordinates,
29
+ "Person": Person,
30
+ "ImageObject": ImageObject,
31
+ "Recipe": Recipe,
32
+ "NutritionInformation": NutritionInformation,
33
+ "Organization": Organization,
34
+ "AggregateRating": AggregateRating,
35
+ "Rating": Rating,
36
+ "Product": Product,
37
+ "Review": Review,
38
+ "VideoObject": VideoObject,
39
+ "AdministrativeArea": AdministrativeArea,
40
+ "Audience": Audience,
41
+ "Place": Place,
42
+ "QuantitativeValue": QuantitativeValue,
43
+ "MonetaryAmount": MonetaryAmount,
44
+ "JobPosting": JobPosting,
45
+ }
@@ -0,0 +1,500 @@
1
+ from typing import Any, List, Optional, Union
2
+ from html import escape
3
+ from datetime import datetime
4
+ from ..base import BaseModel, Field, register_renderer
5
+
6
+
7
+ class URL(BaseModel):
8
+ """
9
+ Corresponds to the JSON-LD "URL" type.
10
+ https://schema.org/URL
11
+ """
12
+ url: str
13
+
14
+ class Meta:
15
+ schema_type: str = "URL"
16
+
17
+ class JobTitle(BaseModel):
18
+ """
19
+ The job title of the person (for example, Financial Manager).
20
+ http://schema.org/jobTitle
21
+
22
+ Example:
23
+ "jobTitle": {
24
+ "@type": "DefinedTerm",
25
+ "inDefinedTermSet": "https://targetjobs.co.uk/careers-advice/job-descriptions",
26
+ "termCode": "277133-aid-workerhumanitarian-worker-job-description",
27
+ "name": "Aid worker/humanitarian worker",
28
+ "url": "https://targetjobs.co.uk/careers-advice/job-descriptions/277133-aid-workerhumanitarian-worker-job-description"
29
+ }
30
+ """ # noqa
31
+ name: str
32
+ url: str
33
+ inDefinedTermSet: str
34
+ termCode: str
35
+
36
+ class Meta:
37
+ schema_type: str = "JobTitle"
38
+
39
+
40
+ class PostalAddress(BaseModel):
41
+ """
42
+ Corresponds to the JSON-LD "Address" type.
43
+ https://schema.org/PostalAddress
44
+
45
+ Example:
46
+ "address": {
47
+ "@type": "PostalAddress",
48
+ "addressLocality": "Seattle",
49
+ "addressRegion": "WA",
50
+ "postalCode": "98052",
51
+ "streetAddress": "20341 Whitworth Institute 405 N. Whitworth"
52
+ }
53
+ """
54
+ streetAddress: str
55
+ addressLocality: str
56
+ addressRegion: str
57
+ postalCode: str
58
+ addressCountry: str
59
+ postOfficeBoxNumber: Optional[str]
60
+ telephone: Optional[str]
61
+
62
+ class Meta:
63
+ schema_type: str = "PostalAddress"
64
+
65
+
66
+ class GeoCoordinates(BaseModel):
67
+ """
68
+ Corresponds to the JSON-LD "GeoCoordinates" type.
69
+ https://schema.org/GeoCoordinates
70
+
71
+ Properties:
72
+ latitude: The latitude of a location. For example 37.42242 (WGS 84).
73
+ longitude: The longitude of a location. For example -122.08585 (WGS 84).
74
+ elevation: The elevation of a location (WGS 84).
75
+ Values may be of the form 'NUMBER UNIT_OF_MEASUREMENT' (e.g., '1,000 m')
76
+ while numbers alone should be assumed to be a value in meters.
77
+
78
+ Example:
79
+ "geo": {
80
+ "@type": "GeoCoordinates",
81
+ "latitude": "47.603",
82
+ "longitude": "-122.329"
83
+ }
84
+ """
85
+ latitude: float
86
+ longitude: float
87
+ elevation: Optional[float]
88
+
89
+ class Meta:
90
+ schema_type: str = "GeoCoordinates"
91
+
92
+
93
+ class Person(BaseModel):
94
+ """
95
+ Corresponds to the JSON-LD "Person" type.
96
+ https://schema.org/Person
97
+
98
+ example:
99
+ {
100
+ "@context": "http://schema.org/",
101
+ "@type": "Person",
102
+ "name": "Jane Doe",
103
+ "jobTitle": "Professor",
104
+ "telephone": "(425) 123-4567",
105
+ "url": "http://www.janedoe.com"
106
+ }
107
+ """
108
+ name: str = Field(required=True)
109
+ image: Union["ImageObject", str, None] = None
110
+ jobTitle: Optional[JobTitle]
111
+ telephone: Optional[str] = None
112
+ url: Optional[str] = None
113
+ sameAs: Optional[str] = None
114
+
115
+ class Meta:
116
+ schema_type: str = "Person"
117
+
118
+
119
+ class ImageObject(BaseModel):
120
+ """
121
+ Corresponds to the JSON-LD "ImageObject" type.
122
+ https://schema.org/ImageObject
123
+ """
124
+ name: str
125
+ url: str = Field(required=True)
126
+ width: int = 0
127
+ height: int = 0
128
+ caption: str = ""
129
+
130
+ class Meta:
131
+ schema_type: str = "ImageObject"
132
+
133
+
134
+ class Recipe(BaseModel):
135
+ """
136
+ Corresponds to the JSON-LD "Recipe" type.
137
+ https://schema.org/Recipe
138
+ """
139
+ name: str
140
+ image: Union["ImageObject", str, None] = None
141
+ datePublished: Optional[str] = None
142
+ description: Optional[str] = None
143
+ prepTime: Optional[str] = None
144
+ cookTime: Optional[str] = None
145
+ totalTime: Optional[str] = None
146
+ recipeIngredient: List[str] = Field(default_factory=list)
147
+ recipeInstructions: List[Any] = Field(default_factory=list)
148
+ recipeCategory: List[str] = Field(default_factory=list)
149
+ recipeCuisine: List[str] = Field(default_factory=list)
150
+
151
+ class Meta:
152
+ schema_type: str = "Recipe"
153
+
154
+
155
+ class NutritionInformation(BaseModel):
156
+ """
157
+ Example of a NutritionInformation data model.
158
+ "nutrition": {
159
+ "@type": "NutritionInformation",
160
+ "calories": "512.2 calories",
161
+ "carbohydrateContent": "67.8 g",
162
+ "cholesterolContent": "30.5 mg",
163
+ "fatContent": "26.7 g",
164
+ "fiberContent": "5 g",
165
+ "proteinContent": "3.6 g",
166
+ "saturatedFatContent": "11.1 g",
167
+ "servingSize": null,
168
+ "sodiumContent": "240.8 mg",
169
+ "sugarContent": "40.3 g",
170
+ "transFatContent": null,
171
+ "unsaturatedFatContent": null
172
+ },
173
+ """
174
+ calories: str
175
+ carbohydrateContent: str
176
+ cholesterolContent: str
177
+ fatContent: str
178
+ fiberContent: str
179
+ proteinContent: str
180
+ saturatedFatContent: str
181
+ servingSize: str
182
+ sodiumContent: str
183
+ sugarContent: str
184
+ transFatContent: str
185
+ unsaturatedFatContent: str
186
+
187
+ class Meta:
188
+ schema_type: str = "NutritionInformation"
189
+
190
+ class Organization(BaseModel):
191
+ name: str = Field(required=True)
192
+ url: str
193
+ sameAs: Optional[List[str]] = None
194
+ logo: Optional[ImageObject] = None
195
+
196
+ class Meta:
197
+ schema_type = "Organization"
198
+
199
+ class Rating(BaseModel):
200
+ """
201
+ Corresponds to the JSON-LD "Rating" type.
202
+ https://schema.org/Rating
203
+ """
204
+ ratingValue: str = Field(required=True)
205
+ bestRating: str
206
+ worstRating: str
207
+
208
+ class Meta:
209
+ schema_type = "Rating"
210
+
211
+
212
+ class AggregateRating(BaseModel):
213
+ """
214
+ Corresponds to the JSON-LD "AggregateRating" type.
215
+ https://schema.org/AggregateRating
216
+ """
217
+ ratingValue: str = Field(required=True)
218
+ reviewCount: str
219
+
220
+ class Meta:
221
+ schema_type = "AggregateRating"
222
+
223
+
224
+ class Product(BaseModel):
225
+ """
226
+ Corresponds to the JSON-LD "Product" type.
227
+ https://schema.org/Product
228
+
229
+ Example:
230
+ {
231
+ "@context": "https://schema.org",
232
+ "@type": "Product",
233
+ "aggregateRating": {
234
+ "@type": "AggregateRating",
235
+ "ratingValue": "3.5",
236
+ "reviewCount": "11"
237
+ },
238
+ "description": "0.7 cubic feet countertop microwave.",
239
+ "name": "Kenmore White 17\" Microwave",
240
+ "image": "kenmore-microwave-17in.jpg",
241
+ "offers": {
242
+ "@type": "Offer",
243
+ "availability": "https://schema.org/InStock",
244
+ "price": "55.00",
245
+ "priceCurrency": "USD"
246
+ },
247
+ }
248
+ """
249
+ name: str = Field(required=True)
250
+ brand: str
251
+ manufacturer: str
252
+ material: str
253
+ model: str
254
+ award: str
255
+ category: str
256
+ gtin: str
257
+ sku: str
258
+ aggregateRating: Optional[AggregateRating]
259
+
260
+ class Meta:
261
+ schema_type = "Product"
262
+
263
+ class Review(BaseModel):
264
+ """
265
+ Corresponds to the JSON-LD "Review" type.
266
+ https://schema.org/Review
267
+
268
+ Example:
269
+ {
270
+ "@type": "Review",
271
+ "datePublished": "2009-10-15T16:20:23.473Z",
272
+ "reviewBody": "My words from 2008 still hold true today.",
273
+ "reviewRating": {
274
+ "@type": "Rating",
275
+ "worstRating": "1",
276
+ "bestRating": "5",
277
+ "ratingValue": 5
278
+ },
279
+ "author": {
280
+ "@type": "Person",
281
+ "name": "RCLYMA",
282
+ "image": null,
283
+ "sameAs": "https://www.allrecipes.com/cook/575406/"
284
+ }
285
+ }
286
+ """
287
+ itemReviewed: Optional[Product]
288
+ reviewRating: Rating
289
+ reviewBody: str = Field(default='')
290
+ author: Person
291
+ datePublished: datetime
292
+ publisher: Organization
293
+
294
+ class Meta:
295
+ schema_type = "Review"
296
+
297
+
298
+ class VideoObject(BaseModel):
299
+ """
300
+ Corresponds to the JSON-LD "VideoObject" type.
301
+ https://schema.org/VideoObject
302
+
303
+ Example:
304
+ "video": {
305
+ "@context": "http://schema.org",
306
+ "@type": "VideoObject",
307
+ "name": "Apple Pie by Grandma Ople",
308
+ "description": "Learn how to make Grandma Ople's apple pie.",
309
+ "uploadDate": "2012-05-09T09:07:12.148Z",
310
+ "duration": "PT4M4.744S",
311
+ "thumbnailUrl": "https://imagesvc.meredithcorp.io/v3/mm/image?url=https%3A%2F%2Fcf-images.us-east-1.prod.boltdns.net%2Fv1%2Fstatic%2F1033249144001%2F571fbd8d-66c7-4521-b002-cbb53ace86e9%2Ff253b5d1-edaf-4dc7-8f0c-954a4259d97f%2F160x90%2Fmatch%2Fimage.jpg",
312
+ "publisher": {
313
+ "@type": "Organization",
314
+ "name": "Allrecipes",
315
+ "url": "https://www.allrecipes.com",
316
+ "logo": {
317
+ "@type": "ImageObject",
318
+ "url": "https://www.allrecipes.com/img/logo.png",
319
+ "width": 209,
320
+ "height": 60
321
+ },
322
+ "sameAs": [
323
+ "https://www.facebook.com/allrecipes",
324
+ "https://twitter.com/Allrecipes",
325
+ "https://www.pinterest.com/allrecipes/",
326
+ "https://www.instagram.com/allrecipes/"
327
+ ]
328
+ },
329
+ "embedUrl": "https://players.brightcove.net/1033249144001/default_default/index.html?videoId=1629100183001"
330
+ }
331
+ """ # noqa
332
+ name: str = Field(required=True)
333
+ description: str = ''
334
+ uploadDate: datetime
335
+ duration: str
336
+ thumbnailUrl: str
337
+ publisher: Organization
338
+ embedUrl: str
339
+
340
+ class Meta:
341
+ schema_type = "VideoObject"
342
+
343
+
344
+ class AdministrativeArea(BaseModel):
345
+ """
346
+ Corresponds to the JSON-LD "AdministrativeArea" type.
347
+ https://schema.org/AdministrativeArea
348
+ """
349
+ name: str = Field(required=True)
350
+ geo: Optional[GeoCoordinates]
351
+ branchCode: Optional[str]
352
+
353
+ class Meta:
354
+ schema_type: str = "AdministrativeArea"
355
+
356
+ class Audience(BaseModel):
357
+ """
358
+ Corresponds to the JSON-LD "Audience" type.
359
+ https://schema.org/Audience
360
+ """
361
+ name: str = Field(required=True)
362
+ audienceType: str
363
+ geographicArea: Optional[AdministrativeArea]
364
+
365
+ class Meta:
366
+ schema_type: str = "Audience"
367
+
368
+
369
+ class Place(BaseModel):
370
+ address: PostalAddress
371
+
372
+ class Meta:
373
+ schema_type = "Place"
374
+
375
+ class QuantitativeValue(BaseModel):
376
+ value: float
377
+ unitText: str
378
+
379
+ class Meta:
380
+ schema_type = "QuantitativeValue"
381
+
382
+ class MonetaryAmount(BaseModel):
383
+ currency: Union[str, float]
384
+ value: QuantitativeValue
385
+
386
+ class Meta:
387
+ schema_type = "MonetaryAmount"
388
+
389
+ class JobPosting(BaseModel):
390
+ title: str
391
+ description: str
392
+ hiringOrganization: Organization
393
+ datePosted: str
394
+ validThrough: str
395
+ jobLocation: Optional[Place]
396
+ baseSalary: Optional[MonetaryAmount]
397
+ qualifications: str
398
+ skills: List[str]
399
+ responsibilities: str
400
+ educationRequirements: str
401
+ experienceRequirements: str
402
+
403
+ class Meta:
404
+ schema_type = "JobPosting"
405
+
406
+
407
+ @register_renderer("ImageObject")
408
+ def render_imageobject(model: "BaseModel", top_level: bool) -> str:
409
+ """
410
+ Render an ImageObject in a custom way:
411
+ <div typeof="ImageObject">
412
+ <h2 property="name">...</h2>
413
+ <img src="..." alt="..." property="contentUrl"/>
414
+ ...
415
+ </div>
416
+ """
417
+ # You can read fields from the model (like name, url, caption, etc.).
418
+ # If you used 'url' as the field that is the image's src, we can do:
419
+ name = getattr(model, "name", None) # or None if not present
420
+ url = getattr(model, "url", None)
421
+ caption = getattr(model, "caption", None)
422
+ width = getattr(model, "width", None)
423
+ height = getattr(model, "height", None)
424
+
425
+ schema_type = getattr(model.Meta, 'schema_type', model.__class__.__name__)
426
+
427
+ container_open = ""
428
+ if top_level:
429
+ container_open = f'<div vocab="https://schema.org/" typeof="{escape(schema_type)}">' # noqa
430
+ else:
431
+ # when nested, we might do property="image" or property=schema_type
432
+ container_open = f'<div property="{escape(schema_type)}" typeof="{escape(schema_type)}">' # noqa
433
+
434
+ pieces = [container_open]
435
+
436
+ # Render name as <h2 property="name">Name</h2> if present
437
+ if name:
438
+ name_esc = escape(str(name))
439
+ pieces.append(f'<h2 property="name">{name_esc}</h2>')
440
+
441
+ # Now create an <img> that has property="contentUrl" or "url"
442
+ if url:
443
+ url_esc = escape(str(url))
444
+ caption_esc = escape(str(caption or ""))
445
+ # alt can come from caption
446
+ snippet = f'<img property="contentUrl" src="{url_esc}" alt="{caption_esc}"'
447
+ if width:
448
+ snippet += f' width="{escape(str(width))}"'
449
+ if height:
450
+ snippet += f' height="{escape(str(height))}"'
451
+ snippet += ' />'
452
+ pieces.append(snippet)
453
+
454
+ # If we have other fields not individually handled, we could do a fallback
455
+ # to the normal field iteration. But for brevity, let's omit that here.
456
+ # E.g. leftover = model.render_remaining_fields(…)
457
+ # pieces.append(leftover)
458
+
459
+ pieces.append('</div>')
460
+ return "\n".join(pieces)
461
+
462
+
463
+ @register_renderer("GeoCoordinates")
464
+ def render_geocoordinates(model: "BaseModel", top_level: bool) -> str:
465
+ """
466
+ Custom renderer for GeoCoordinates:
467
+ <div property="geo" typeof="GeoCoordinates">
468
+ <meta property="latitude" content="40.75"/>
469
+ <meta property="longitude" content="-73.98"/>
470
+ ...
471
+ </div>
472
+ """
473
+ lat = getattr(model, "latitude", None)
474
+ lng = getattr(model, "longitude", None)
475
+ elevation = getattr(model, "elevation", None)
476
+
477
+ schema_type = getattr(model.Meta, 'schema_type', model.__class__.__name__)
478
+ if top_level:
479
+ container_open = f'<div vocab="https://schema.org/" typeof="{escape(schema_type)}">' # noqa
480
+ else:
481
+ container_open = f'<div property="{escape(schema_type)}" typeof="{escape(schema_type)}">' # noqa
482
+
483
+ pieces = [container_open]
484
+
485
+ # For numeric fields, we might prefer <meta ... content="..."/>
486
+ if lat is not None:
487
+ pieces.append(
488
+ f'<meta property="latitude" content="{escape(str(lat))}" />'
489
+ )
490
+ if lng is not None:
491
+ pieces.append(
492
+ f'<meta property="longitude" content="{escape(str(lng))}" />'
493
+ )
494
+ if elevation is not None:
495
+ pieces.append(
496
+ f'<meta property="elevation" content="{escape(str(elevation))}" />'
497
+ )
498
+
499
+ pieces.append('</div>')
500
+ return "\n".join(pieces)
@@ -0,0 +1 @@
1
+ from .mapping import ClassDict, ClassDictConfig