cs-tagged 20260912__tar.gz

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.
@@ -0,0 +1,652 @@
1
+ Metadata-Version: 2.4
2
+ Name: cs-tagged
3
+ Version: 20260912
4
+ Summary: Tagged information entities, built on `TagSet`s for representation and typically an `SQLTags` for storage. I use these to persist and mediate knowledge, including my interactions with web sites, APIs, and third party databases.
5
+ Keywords: python3
6
+ Author-email: Cameron Simpson <cs@cskk.id.au>
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
16
+ Requires-Dist: cs.cmdutils>=20260912
17
+ Requires-Dist: cs.context>=20250528
18
+ Requires-Dist: cs.deco>=20260912
19
+ Requires-Dist: cs.lex>=20260912
20
+ Requires-Dist: cs.logutils>=20260912
21
+ Requires-Dist: cs.obj>=20260912
22
+ Requires-Dist: cs.pfx>=20260912
23
+ Requires-Dist: cs.progress>=20260531
24
+ Requires-Dist: cs.tagset>=20260912
25
+ Requires-Dist: cs.trace
26
+ Requires-Dist: icontract
27
+ Requires-Dist: typeguard
28
+ Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
29
+ Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
30
+ Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
31
+ Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/tagged.py
32
+
33
+ Tagged information entities, built on `TagSet`s for representation
34
+ and typically an `SQLTags` for storage.
35
+ I use these to persist and mediate knowledge, including my interactions
36
+ with web sites, APIs, and third party databases.
37
+
38
+ *Latest release 20260912*:
39
+ First PyPI release: Entity, Entities, ScanData.
40
+
41
+
42
+
43
+ Short summary:
44
+
45
+
46
+ * `Entities`: A mixin to support classes which use a `.tagsets:BaseTagSets` attribute to store their data.
47
+
48
+
49
+ * `Entity`: A base class for classes which have a `.tags:TagSet` attribute and a `.tags_db:BaseTagSets` containing other `Tagset`s.
50
+
51
+
52
+ * `ScanData`: A class to manage data obtained about `SiteEntity` instances, for example from an API or scanning a web page.
53
+
54
+ # Classes
55
+
56
+ ## class Entities
57
+
58
+ A mixin to support classes which use a `.tagsets:BaseTagSets` attribute to store their data.
59
+
60
+ Subclasses may define the following class attributes:
61
+ - `EntityClass`: a subclass of `Entity` which represents data entities;
62
+ the default is `Entity` which should be enough if there is no `.tYPE_ZONE`
63
+ - `TYPE_ZONE`: the type zone identifying entities in the
64
+ larger `BaseTagSets` data; if this is not supplied it is
65
+ obtained from `EntityClass.TYPE_ZONE`, if defined
66
+
67
+ A typical use subclasses `cs.sqltags.UsesSQLTags`, a subclass
68
+ of this which uses an `SQLTags` as the storage backend.
69
+
70
+ If there is a `.TYPE_ZONE`, the meaning of the type *zone*,
71
+ *subname* and *key* are as described for the `ZonedTypes`
72
+ class.
73
+
74
+ ### `Entities.EntityClass`
75
+
76
+ A base class for classes which have a `.tags:TagSet` attribute
77
+ and a `.tags_db:BaseTagSets` containing other `Tagset`s.
78
+
79
+ Usually these are considered part of a "zone" - a group of
80
+ entities in a particular applicaiton domain.
81
+
82
+ The subclass may itself define its `.tags` instance attribute
83
+ or rely on the default cached property `.tags`, which will return
84
+ `self.tags_db[self.tags_entity_key]`.
85
+ (`self.tags_entity_key` is `self.tags.name` by default.)
86
+
87
+ Note that this mixin brings its own `__new__` method which
88
+ can choose a subclass based on the subclass' `.TYPE_SUBNAME`
89
+ attribute. See the `__new__` docstring.
90
+
91
+ This also provides some behaviour based around updating
92
+ entities based on some kind of API call; the direct values
93
+ from the API call land on attributes named `{zone}.{key}` -
94
+ the `.type_zone_update(mapping)` provides a convenient call
95
+ for this.
96
+
97
+ `Entity` instances are designed as representing entities in
98
+ some "zone", a set of entities in some domain or organised
99
+ grouping; typical examples include entities describes by some
100
+ API like MusicBrainzNG or objects presented by some website.
101
+ As such, they subclass `ZonedTypes`, which expects the entity's
102
+ `.name` to be of the form *zone*`.`*subname*`.`*key*; the
103
+ *zone* partitions entities off into their own domain, the
104
+ *subname* is in effect the entity's type within that domain
105
+ and the *key* is the entity id within that type.
106
+
107
+ On this basis, entities updated with data from the zone,
108
+ for example from an API call or by scraping a web page,
109
+ normally update tag keys named *zone*`.`*field* where the *field*
110
+ is the top level field from the data.
111
+
112
+ The `ZonedTypes.__getattr__(attr)` method looks first for a
113
+ direct tag named `attr` but falls back to a tag named
114
+ *zone*`.`*attr*. This allows entities to be tagged with the
115
+ data from an API, but to be overridden by the direct tag if
116
+ the API data are considered incorrect or unsuitable.
117
+
118
+ The `ScanData.apply()` method follows this principle,
119
+ applying the scanned data to tags named *zone*`.`*field*.
120
+
121
+ We relate entities using attributes named *field*`_id`,
122
+ which may be a single key for another entity or a list of keys.
123
+
124
+ Various derived attributes are also provided, see the
125
+ `__getattr__` docstring for details:
126
+ - *field*`_ent`: the related `Entity` named *zone*`.`*field*`.`*key*
127
+ where *key* comes from the `.`*field*`_id` attribute
128
+ - *field*`_ents`: multiple related `Entity` named
129
+ *zone*`.`*field*`.`*key* where *key* comes from the `.`*field*`_id` attribute
130
+
131
+ ### `Entities.TagsetsClass`
132
+
133
+ The type of the None singleton.
134
+
135
+ ### `Entities.__class_getitem__(index)`
136
+
137
+ An `Entities` subclass may be indexed with a string.
138
+
139
+ If there is no `cls.TYPE_ZONE` the string is treated either as:
140
+ - if the string ha no dots, a `TYPE_ZONE` value - the
141
+ `Entities` instance for that zone is returned
142
+ - if the string has dots, as an `Entity.name` and looked
143
+ up with `cls.by_entity_id(index)`.
144
+
145
+ If there is a `cls.TYPE_ZONE`, such as with a `SiteMap`,
146
+ the string is treated as a `ZonedTypes.type_zone_key` and
147
+ looked up as by indexing that zone's `Entities` instance.
148
+
149
+ Example using `TheTVDBAPI`, which has a `TYPE_ZONE`:
150
+
151
+ # fetch the TV series entity with id 1234
152
+ # there is a TheTVDBAPI.TYPE_ZONE
153
+ series = TheTVDBAPI['series.1234']
154
+
155
+ # fetch an arbtrary Entity
156
+ # the value of `TheTVDBAPI.TYPE_ZONE` is "tvdb"
157
+ series = Entities['tvdb.series.1234']
158
+
159
+ Example using `SiteMap`, the base class for site maps, and
160
+ which has no `.TYPE_ZONE`:
161
+
162
+ smh_map = SiteMap['smh']
163
+ smh_topic = SiteMap['smh.topic.technology']
164
+ smh_article = SiteMap['smh']['article.abcd']
165
+
166
+ ### `Entities.__dict__`
167
+
168
+ Read-only proxy of a mapping.
169
+
170
+ ### `Entities.__firstlineno__`
171
+
172
+ int([x]) -> integer
173
+ int(x, base=10) -> integer
174
+
175
+ Convert a number or string to an integer, or return 0 if no arguments
176
+ are given. If x is a number, return x.__int__(). For floating-point
177
+ numbers, this truncates towards zero.
178
+
179
+ If x is not a number or if base is given, then x must be a string,
180
+ bytes, or bytearray instance representing an integer literal in the
181
+ given base. The literal can be preceded by '+' or '-' and be surrounded
182
+ by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
183
+ Base 0 means to interpret the base from the string as an integer literal.
184
+ >>> int('0b100', base=0)
185
+ 4
186
+
187
+ ### `Entities.__getitem__(self, index: str | tuple[str, str | int] | tuple[str, str, str | int]) -> cs.tagged.Entity`
188
+
189
+ `self.__getitem__(index)` calls `self.entity(index)`.
190
+
191
+ ### `Entities.__init_subclass__(**kw)`
192
+
193
+ Inititialise a subclass by defining `.TYPE_ZNE` if already present.
194
+
195
+ ### `Entities.__static_attributes__`
196
+
197
+ Built-in immutable sequence.
198
+
199
+ If no argument is given, the constructor returns an empty tuple.
200
+ If iterable is specified the tuple is initialized from iterable's items.
201
+
202
+ If the argument is a tuple, the return value is the same object.
203
+
204
+ ### `Entities.as_zone(self, zone=None)`
205
+
206
+ Push this `Entities` instance as the default mapping for
207
+ `zone`, whose default is `self.__class__.TYPE_ZONE`.
208
+ Yields the zone, or `None` if there is no
209
+
210
+ ### `Entities.by_entity_id(entity_id: str) -> cs.tagged.Entity`
211
+
212
+ Return the `Entity` instance corresponding to `entity_id`
213
+ from the full tb
214
+ Raise `ValueError` if `entity_id` cannot be parsed by
215
+ `ZonedTypes.type_parts_of`.
216
+ Raise `KeyError` if there is no `Entities` instance for the zone
217
+ and we cannot make a default instance.
218
+
219
+ ### `Entities.by_type_zone`
220
+
221
+ Mapping class that references values weakly.
222
+
223
+ Entries in the dictionary will be discarded when no strong
224
+ reference to the value exists anymore
225
+
226
+ ### `Entities.class_by_type_zone`
227
+
228
+ dict() -> new empty dictionary
229
+ dict(mapping) -> new dictionary initialized from a mapping object's
230
+ (key, value) pairs
231
+ dict(iterable) -> new dictionary initialized as if via:
232
+ d = {}
233
+ for k, v in iterable:
234
+ d[k] = v
235
+ dict(**kwargs) -> new dictionary initialized with the name=value pairs
236
+ in the keyword argument list. For example: dict(one=1, two=2)
237
+
238
+ ### `Entities.default(zone: str | None = None) -> 'Entities'`
239
+
240
+ Return the default `Entities` instance for `zone`.
241
+ If `zone` is not defined it is taken from `cls.TYPE_ZONE`.
242
+ Raise `KeyError` for an unregistered `zone`.
243
+ Raise `TypeError` if there is no registered default
244
+ and the class for `zone` cannot be instantiated with `entcls()`.
245
+
246
+ ### `Entities.entity(self, index: str | tuple[str, str | int] | tuple[str, str, str | int], zone=None) -> cs.tagged.Entity`
247
+
248
+ Fetch the `Entity` instance for the supplied `index`.
249
+ This underlies the `__getitem__` method.
250
+
251
+ The meaning of the type *zone*, *subname* and *key* are as
252
+ described for the `ZonedTypes` class.
253
+
254
+ The `index` may take the following forms:
255
+ - `str`: a string which will be split into *subname* and *key*
256
+ for use in `self.TYPE_ZONE`
257
+ - `(subname,key)`: a 2-tuple of the type *subname* and *key*
258
+ in `self.TYPE_ZONE`
259
+ the subname make also be a subclass of `self.EntityClass`
260
+ - `(zone,subname,key)`: a 3-tuple of the type zone, subname and key
261
+ The *subname* may also be a class (normally a subclass of
262
+ `Entity`, usually a subclass of `type(self).EntityClass`);
263
+ in this case the *subname* will be taken from `type(self).TYPE_SUBNAME`
264
+ attribute.
265
+ The *key* may also be an `int` or a `uuid.UUID`, in which
266
+ case it will be used as `str(key)`.
267
+
268
+ Examples:
269
+
270
+ # the Entity subclass Artist, and the Entities
271
+ # subclass MBDB which hold MusicbrainzNG information
272
+ from cs.cdrip import Artist, MBDB
273
+ mbdb = MBDB()
274
+
275
+ # Various indices obtaining the record for Jon Cleary,
276
+ # whose key is 'mbdb.artist.a417f0e5-2c14-445a-9a07-5a7ad2bdeafa'
277
+
278
+ # the subname.key as a single string
279
+ artist = mbdb['artist.a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
280
+
281
+ # the subname and key in a 2-tuple
282
+ artist = mbdb['artist', 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
283
+
284
+ # the record but not from the default MBDB zonne
285
+ artist = mbdb['mbdb2', 'artist', 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
286
+
287
+ # the preferred way to obtain it, using the entity type
288
+ artist = mbdb[Artist, 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
289
+
290
+ # or if you're working with UUIDs
291
+ artist_uuid = UUID('a417f0e5-2c14-445a-9a07-5a7ad2bdeafa')
292
+ artist = mbdb[Artist, artist_uuid]
293
+
294
+ ### `Entities.find(self, *criteria, **crit_kw) -> list[cs.tagged.Entity]`
295
+
296
+ Find entities in the database.
297
+
298
+ This calls `self.tagsets.find()` and returns the associated
299
+ `Entity` instances.
300
+
301
+ ### `Entities.keys(self, subname=None)`
302
+
303
+ Return the keys from `self.tagsets` as `(subname,type_key)` 2-tuples
304
+ suitable as indices of `self`.
305
+ If `subname` is not `None`, restrict the keys to those with that subname.
306
+
307
+ ### `Entities.set_as_zone(self, zone: str, if_unset=False)`
308
+
309
+ Set this `Entities` instance as the one handling entities in `zone`.
310
+
311
+ ### `Entities.zone_entity(self, zone: str) -> 'Entity'`
312
+
313
+ Return the `Entity` entity associated with a per-type-zone key.
314
+ For example, `self.zone_entity('tvdb')` would return the entity
315
+ for `tvdb.`*tvdb_id* where `tvdb_id` comes from `self['id.tvdb']`.
316
+
317
+ ## class Entity(cs.tagset.ZonedTypes, cs.obj.Refreshable, cs.deco.Promotable, cs.lex.FormatableMixin, cs.obj.NoAttrs)
318
+
319
+ A base class for classes which have a `.tags:TagSet` attribute
320
+ and a `.tags_db:BaseTagSets` containing other `Tagset`s.
321
+
322
+ Usually these are considered part of a "zone" - a group of
323
+ entities in a particular applicaiton domain.
324
+
325
+ The subclass may itself define its `.tags` instance attribute
326
+ or rely on the default cached property `.tags`, which will return
327
+ `self.tags_db[self.tags_entity_key]`.
328
+ (`self.tags_entity_key` is `self.tags.name` by default.)
329
+
330
+ Note that this mixin brings its own `__new__` method which
331
+ can choose a subclass based on the subclass' `.TYPE_SUBNAME`
332
+ attribute. See the `__new__` docstring.
333
+
334
+ This also provides some behaviour based around updating
335
+ entities based on some kind of API call; the direct values
336
+ from the API call land on attributes named `{zone}.{key}` -
337
+ the `.type_zone_update(mapping)` provides a convenient call
338
+ for this.
339
+
340
+ `Entity` instances are designed as representing entities in
341
+ some "zone", a set of entities in some domain or organised
342
+ grouping; typical examples include entities describes by some
343
+ API like MusicBrainzNG or objects presented by some website.
344
+ As such, they subclass `ZonedTypes`, which expects the entity's
345
+ `.name` to be of the form *zone*`.`*subname*`.`*key*; the
346
+ *zone* partitions entities off into their own domain, the
347
+ *subname* is in effect the entity's type within that domain
348
+ and the *key* is the entity id within that type.
349
+
350
+ On this basis, entities updated with data from the zone,
351
+ for example from an API call or by scraping a web page,
352
+ normally update tag keys named *zone*`.`*field* where the *field*
353
+ is the top level field from the data.
354
+
355
+ The `ZonedTypes.__getattr__(attr)` method looks first for a
356
+ direct tag named `attr` but falls back to a tag named
357
+ *zone*`.`*attr*. This allows entities to be tagged with the
358
+ data from an API, but to be overridden by the direct tag if
359
+ the API data are considered incorrect or unsuitable.
360
+
361
+ The `ScanData.apply()` method follows this principle,
362
+ applying the scanned data to tags named *zone*`.`*field*.
363
+
364
+ We relate entities using attributes named *field*`_id`,
365
+ which may be a single key for another entity or a list of keys.
366
+
367
+ Various derived attributes are also provided, see the
368
+ `__getattr__` docstring for details:
369
+ - *field*`_ent`: the related `Entity` named *zone*`.`*field*`.`*key*
370
+ where *key* comes from the `.`*field*`_id` attribute
371
+ - *field*`_ents`: multiple related `Entity` named
372
+ *zone*`.`*field*`.`*key* where *key* comes from the `.`*field*`_id` attribute
373
+
374
+ ### `Entity.__annotations__`
375
+
376
+ dict() -> new empty dictionary
377
+ dict(mapping) -> new dictionary initialized from a mapping object's
378
+ (key, value) pairs
379
+ dict(iterable) -> new dictionary initialized as if via:
380
+ d = {}
381
+ for k, v in iterable:
382
+ d[k] = v
383
+ dict(**kwargs) -> new dictionary initialized with the name=value pairs
384
+ in the keyword argument list. For example: dict(one=1, two=2)
385
+
386
+ ### `Entity.__delitem__(self, tag_name: str)`
387
+
388
+ Remove an entry from `self.tags`.
389
+
390
+ ### `Entity.__firstlineno__`
391
+
392
+ int([x]) -> integer
393
+ int(x, base=10) -> integer
394
+
395
+ Convert a number or string to an integer, or return 0 if no arguments
396
+ are given. If x is a number, return x.__int__(). For floating-point
397
+ numbers, this truncates towards zero.
398
+
399
+ If x is not a number or if base is given, then x must be a string,
400
+ bytes, or bytearray instance representing an integer literal in the
401
+ given base. The literal can be preceded by '+' or '-' and be surrounded
402
+ by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
403
+ Base 0 means to interpret the base from the string as an integer literal.
404
+ >>> int('0b100', base=0)
405
+ 4
406
+
407
+ ### `Entity.__getattr__(self, attr)`
408
+
409
+ Try `ZonedTypes.__getattr__` (which lokks up `[attr]` then `[f'{zone}.{attr}']`)
410
+ then fall back to suffix based synthetic attributes where
411
+ an attribute ending in `_`*suffix* is implemented by the
412
+ `suffix_`*suffix*`(attr)` method if it exists.
413
+
414
+ The following synthetic attibutes are implemented:
415
+ - *attr0*`_or_none`: return `.attr0` or `None` if that does not exist
416
+ - *subtype*`_ent`: the entity with name
417
+ *type_zone*`.`*subtype*`.`*id* or `None` where `id` comes
418
+ from the `.`*attr*`_id` value;
419
+ see the `suffix_ent` method.
420
+ - *subtype*`_ents`: the entities with name
421
+ *type_zone*`.`*subtype*`.`*id* or `None` where each `id` comes
422
+ from the `.`*attr*`_id` values;
423
+ see the `suffix_ents` method.
424
+
425
+ ### `Entity.__getitem__(self, tag_name: str)`
426
+
427
+ Index `self.tags`.
428
+
429
+ ### `Entity.__setitem__(self, tag_name, value, *, verbose=False)`
430
+
431
+ Set a tag value.
432
+
433
+ ### `Entity.__static_attributes__`
434
+
435
+ Built-in immutable sequence.
436
+
437
+ If no argument is given, the constructor returns an empty tuple.
438
+ If iterable is specified the tuple is initialized from iterable's items.
439
+
440
+ If the argument is a tuple, the return value is the same object.
441
+
442
+ ### `Entity.as_dict(self)`
443
+
444
+ Proxy `.as_dict()` to `self.tags`.
445
+
446
+ ### `Entity.entity`
447
+
448
+ The `.entity` attribute space, whose attributes map to
449
+ entities which are `UsesTags` instances from the appropriate
450
+ `Entities` instances according to their zone.
451
+
452
+ Example:
453
+
454
+ tags = TagSet({'id.playon':'recording.1234567'})
455
+ playon_recording = tags.entity.playon
456
+
457
+ ### `Entity.entity_`
458
+
459
+ The `.entity_` attribute space, whose attributes map to
460
+ entities which are `UsesTags` instances from the appropriate
461
+ `Entities` instances according to their zone.
462
+ Unlike `.entity`, a missing `id.` tag returns `None` instead
463
+ of raising `AttributeError`.
464
+
465
+ Example:
466
+
467
+ tags = TagSet({'id.playon':'recording.1234567'})
468
+ playon_recording = tags.entity.playon
469
+
470
+ ### `Entity.format_attributes`
471
+
472
+ dict() -> new empty dictionary
473
+ dict(mapping) -> new dictionary initialized from a mapping object's
474
+ (key, value) pairs
475
+ dict(iterable) -> new dictionary initialized as if via:
476
+ d = {}
477
+ for k, v in iterable:
478
+ d[k] = v
479
+ dict(**kwargs) -> new dictionary initialized with the name=value pairs
480
+ in the keyword argument list. For example: dict(one=1, two=2)
481
+
482
+ ### `Entity.format_kwargs(self)`
483
+
484
+ A `format_kwargs` method to support `cs.lex.FormatableMixin`.
485
+
486
+ ### `Entity.get(self, tag_name: str, default=None)`
487
+
488
+ Call `.tags.get(tag_name)`.
489
+
490
+ ### `Entity.items(self)`
491
+
492
+ The tags items.
493
+
494
+ ### `Entity.prefix_in(self, attr) -> collections.abc.Sequence[typing.Self]`
495
+
496
+ Resolve `in_`*subtype*[`_`*field* to the `Entity` instance
497
+ of subtype *subtype* whose *field*`_id` attribute contains
498
+ `self.type_key`.
499
+ The default *field* is `self.type_subname`.
500
+
501
+ For example, if `self.name` is `"tvdb.episode.1234"` then
502
+ `self.in_season` would return a list of all the `tvdb.season`
503
+ entities whose `episode_id` attributes referred to `1234`.
504
+
505
+ Where the
506
+
507
+ ### `Entity.print(self)`
508
+
509
+ The default `print()` runs `self.printt()`.
510
+ This is intended to be a nice print of important stuff.
511
+
512
+ ### `Entity.refresh_key(self)`
513
+
514
+ The unique key identifying this object for use in recursive refreshes.
515
+
516
+ ### `Entity.refresh_last_update`
517
+
518
+ The last time a refresh update time.
519
+
520
+ ### `Entity.setdefault(self, key, default_value)`
521
+
522
+ Set `self[key]=default_value` if `key` is not present.
523
+
524
+ ### `Entity.suffix_ent(self, attr) -> Optional[Self]`
525
+
526
+ Resolve *subtype*`_ent` to `self[type_zone.`*subtype*`.id]`
527
+ or `None` if no `self[`*subtype*`_id]`
528
+
529
+ ### `Entity.suffix_ents(self, attr) -> collections.abc.Sequence[typing.Self]`
530
+
531
+ Resolve *subtype*`_ents` to [self[type_zone.`*subtype*`.id]]`
532
+ or `()` if no `self[`*subtype*`_id]]`.
533
+
534
+ ### `Entity.tags`
535
+
536
+ A default `.tags` property which obtains a `TagSet` from `self.tags_db`
537
+ via using the `TagSet` name `self.tags_entity_key`.
538
+ This is for subclasses which might fetch the `.tags` on demand.
539
+
540
+ Subclasses typically set `.tags` during `__init__` and
541
+ therefore have no need for a `.tags_entity_key` property.
542
+
543
+ ### `Entity.tags_entity_key`
544
+
545
+ Our tagged entity key, `self.tags.name`.
546
+
547
+ This is only really needed by the `.tags` cached
548
+ property; most subclasses of `Entity` set `.tags` during
549
+ `__init__`.
550
+ If you have an "on demand" subclass you should override
551
+ this method to compute the entity key without relying on
552
+ the (missing) `.tags` attribute.
553
+
554
+ ### `Entity.type_zone_update(self, mapping, prefix=None, *, lc_=False)`
555
+
556
+ Update `self` with `mapping`, using `prefix`.
557
+ The default `prefix` is self.type_zone`.
558
+
559
+ ### `Entity.update(self, *update_a, **update_kw)`
560
+
561
+ Update the tags, tupically from a mapping or keyword arguments.
562
+
563
+ ### `Entity.values(self)`
564
+
565
+ The tags values.
566
+
567
+ ## class ScanData
568
+
569
+ A class to manage data obtained about `SiteEntity` instances,
570
+ for example from an API or scanning a web page.
571
+
572
+ The data for an `SiteEntity` can be obtained by indexing the
573
+ `ScanData` instance with a `SiteEntity` instance or
574
+ a `(ent_cls,type_key)` 2-tuple
575
+
576
+ ### `ScanData.__dict__`
577
+
578
+ Read-only proxy of a mapping.
579
+
580
+ ### `ScanData.__firstlineno__`
581
+
582
+ int([x]) -> integer
583
+ int(x, base=10) -> integer
584
+
585
+ Convert a number or string to an integer, or return 0 if no arguments
586
+ are given. If x is a number, return x.__int__(). For floating-point
587
+ numbers, this truncates towards zero.
588
+
589
+ If x is not a number or if base is given, then x must be a string,
590
+ bytes, or bytearray instance representing an integer literal in the
591
+ given base. The literal can be preceded by '+' or '-' and be surrounded
592
+ by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
593
+ Base 0 means to interpret the base from the string as an integer literal.
594
+ >>> int('0b100', base=0)
595
+ 4
596
+
597
+ ### `ScanData.__getitem__(self, ent: Union[tuple, ForwardRef('Entity')])`
598
+
599
+ The data for the supplied `ent`.
600
+
601
+ ### `ScanData.__iter__(self)`
602
+
603
+ Iteration yields `(Entity,datadict)` 2-tuples.
604
+
605
+ ### `ScanData.__static_attributes__`
606
+
607
+ Built-in immutable sequence.
608
+
609
+ If no argument is given, the constructor returns an empty tuple.
610
+ If iterable is specified the tuple is initialized from iterable's items.
611
+
612
+ If the argument is a tuple, the return value is the same object.
613
+
614
+ ### `ScanData.apply(self, *refresh_ents)`
615
+
616
+ Apply the scanned data to its entities.
617
+
618
+ If an entity `ent` is a member of `refresh_ents` then call
619
+ `ent.refresh(data=data)` on the basis that the data are
620
+ complete enough to consider the entity refreshed, otherwise
621
+ call `ent.type_zone_update(data)`.
622
+
623
+ The purpose of the call to `ent.refresh()` is to exercise
624
+ the refresh machinery. On a `Refreshable` object `ent` this
625
+ marks the object as current with the new data; the data are
626
+ applied with `Refreshable._refresh()`, the zone specific
627
+ method, which typically _also_ uses `ent.type_zone_update(data)`.
628
+
629
+ This follows the tag name design outlined in the `Entity` docstring,
630
+ where API/site data are stored with tags named *zone*`.`*field*.
631
+
632
+ ### `ScanData.conv(self, ent: Union[tuple, ForwardRef('Entity')], mapping, key, conv=None)`
633
+
634
+ Update the data for `ent` from `mapping[key]` if present.
635
+ If `conv` is not `None` it should be a callable accepting
636
+ the value from `mapping[key]` and returning a converted
637
+ value to store in the entity data.
638
+
639
+ ### `ScanData.printt(self, title=None)`
640
+
641
+ Call `cs.lex.printt()` to print the scanned data.
642
+
643
+ ### `ScanData.update(self, ent: Union[tuple[type, int | str], ForwardRef('Entity')], **data_kw)`
644
+
645
+ Update the data for `ent` from `data_kw`.
646
+
647
+ # Release Log
648
+
649
+
650
+
651
+ *Release 20260912*:
652
+ First PyPI release: Entity, Entities, ScanData.