Tagged information entities, built on TagSets 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.
Latest release 20260912: First PyPI release: Entity, Entities, ScanData.
Short summary:
-
Entities: A mixin to support classes which use a.tagsets:BaseTagSetsattribute to store their data. -
Entity: A base class for classes which have a.tags:TagSetattribute and a.tags_db:BaseTagSetscontaining otherTagsets. -
ScanData: A class to manage data obtained aboutSiteEntityinstances, for example from an API or scanning a web page.
Classes
class Entities
A mixin to support classes which use a .tagsets:BaseTagSets attribute to store their data.
Subclasses may define the following class attributes:
EntityClass: a subclass ofEntitywhich represents data entities; the default isEntitywhich should be enough if there is no.tYPE_ZONETYPE_ZONE: the type zone identifying entities in the largerBaseTagSetsdata; if this is not supplied it is obtained fromEntityClass.TYPE_ZONE, if defined
A typical use subclasses cs.sqltags.UsesSQLTags, a subclass
of this which uses an SQLTags as the storage backend.
If there is a .TYPE_ZONE, the meaning of the type zone,
subname and key are as described for the ZonedTypes
class.
Entities.EntityClass
A base class for classes which have a .tags:TagSet attribute
and a .tags_db:BaseTagSets containing other Tagsets.
Usually these are considered part of a "zone" - a group of entities in a particular applicaiton domain.
The subclass may itself define its .tags instance attribute
or rely on the default cached property .tags, which will return
self.tags_db[self.tags_entity_key].
(self.tags_entity_key is self.tags.name by default.)
Note that this mixin brings its own __new__ method which
can choose a subclass based on the subclass' .TYPE_SUBNAME
attribute. See the __new__ docstring.
This also provides some behaviour based around updating
entities based on some kind of API call; the direct values
from the API call land on attributes named {zone}.{key} -
the .type_zone_update(mapping) provides a convenient call
for this.
Entity instances are designed as representing entities in
some "zone", a set of entities in some domain or organised
grouping; typical examples include entities describes by some
API like MusicBrainzNG or objects presented by some website.
As such, they subclass ZonedTypes, which expects the entity's
.name to be of the form zone.subname.key; the
zone partitions entities off into their own domain, the
subname is in effect the entity's type within that domain
and the key is the entity id within that type.
On this basis, entities updated with data from the zone,
for example from an API call or by scraping a web page,
normally update tag keys named zone.field where the field
is the top level field from the data.
The ZonedTypes.__getattr__(attr) method looks first for a
direct tag named attr but falls back to a tag named
zone.attr. This allows entities to be tagged with the
data from an API, but to be overridden by the direct tag if
the API data are considered incorrect or unsuitable.
The ScanData.apply() method follows this principle,
applying the scanned data to tags named zone.field.
We relate entities using attributes named field_id,
which may be a single key for another entity or a list of keys.
Various derived attributes are also provided, see the
__getattr__ docstring for details:
- field
_ent: the relatedEntitynamed zone.field.key where key comes from the.field_idattribute - field
_ents: multiple relatedEntitynamed zone.field.key where key comes from the.field_idattribute
Entities.TagsetsClass
The type of the None singleton.
Entities.__class_getitem__(index)
An Entities subclass may be indexed with a string.
If there is no cls.TYPE_ZONE the string is treated either as:
- if the string ha no dots, a
TYPE_ZONEvalue - theEntitiesinstance for that zone is returned - if the string has dots, as an
Entity.nameand looked up withcls.by_entity_id(index).
If there is a cls.TYPE_ZONE, such as with a SiteMap,
the string is treated as a ZonedTypes.type_zone_key and
looked up as by indexing that zone's Entities instance.
Example using TheTVDBAPI, which has a TYPE_ZONE:
# fetch the TV series entity with id 1234
# there is a TheTVDBAPI.TYPE_ZONE
series = TheTVDBAPI['series.1234']
# fetch an arbtrary Entity
# the value of `TheTVDBAPI.TYPE_ZONE` is "tvdb"
series = Entities['tvdb.series.1234']
Example using SiteMap, the base class for site maps, and
which has no .TYPE_ZONE:
smh_map = SiteMap['smh']
smh_topic = SiteMap['smh.topic.technology']
smh_article = SiteMap['smh']['article.abcd']
Entities.__dict__
Read-only proxy of a mapping.
Entities.__firstlineno__
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
int('0b100', base=0) 4
Entities.__getitem__(self, index: str | tuple[str, str | int] | tuple[str, str, str | int]) -> cs.tagged.Entity
self.__getitem__(index) calls self.entity(index).
Entities.__init_subclass__(**kw)
Inititialise a subclass by defining .TYPE_ZNE if already present.
Entities.__static_attributes__
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
Entities.as_zone(self, zone=None)
Push this Entities instance as the default mapping for
zone, whose default is self.__class__.TYPE_ZONE.
Yields the zone, or None if there is no
Entities.by_entity_id(entity_id: str) -> cs.tagged.Entity
Return the Entity instance corresponding to entity_id
from the full tb
Raise ValueError if entity_id cannot be parsed by
ZonedTypes.type_parts_of.
Raise KeyError if there is no Entities instance for the zone
and we cannot make a default instance.
Entities.by_type_zone
Mapping class that references values weakly.
Entries in the dictionary will be discarded when no strong reference to the value exists anymore
Entities.class_by_type_zone
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
Entities.default(zone: str | None = None) -> 'Entities'
Return the default Entities instance for zone.
If zone is not defined it is taken from cls.TYPE_ZONE.
Raise KeyError for an unregistered zone.
Raise TypeError if there is no registered default
and the class for zone cannot be instantiated with entcls().
Entities.entity(self, index: str | tuple[str, str | int] | tuple[str, str, str | int], zone=None) -> cs.tagged.Entity
Fetch the Entity instance for the supplied index.
This underlies the __getitem__ method.
The meaning of the type zone, subname and key are as
described for the ZonedTypes class.
The index may take the following forms:
str: a string which will be split into subname and key for use inself.TYPE_ZONE(subname,key): a 2-tuple of the type subname and key inself.TYPE_ZONEthe subname make also be a subclass ofself.EntityClass(zone,subname,key): a 3-tuple of the type zone, subname and key The subname may also be a class (normally a subclass ofEntity, usually a subclass oftype(self).EntityClass); in this case the subname will be taken fromtype(self).TYPE_SUBNAMEattribute. The key may also be anintor auuid.UUID, in which case it will be used asstr(key).
Examples:
# the Entity subclass Artist, and the Entities
# subclass MBDB which hold MusicbrainzNG information
from cs.cdrip import Artist, MBDB
mbdb = MBDB()
# Various indices obtaining the record for Jon Cleary,
# whose key is 'mbdb.artist.a417f0e5-2c14-445a-9a07-5a7ad2bdeafa'
# the subname.key as a single string
artist = mbdb['artist.a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
# the subname and key in a 2-tuple
artist = mbdb['artist', 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
# the record but not from the default MBDB zonne
artist = mbdb['mbdb2', 'artist', 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
# the preferred way to obtain it, using the entity type
artist = mbdb[Artist, 'a417f0e5-2c14-445a-9a07-5a7ad2bdeafa']
# or if you're working with UUIDs
artist_uuid = UUID('a417f0e5-2c14-445a-9a07-5a7ad2bdeafa')
artist = mbdb[Artist, artist_uuid]
Entities.find(self, *criteria, **crit_kw) -> list[cs.tagged.Entity]
Find entities in the database.
This calls self.tagsets.find() and returns the associated
Entity instances.
Entities.keys(self, subname=None)
Return the keys from self.tagsets as (subname,type_key) 2-tuples
suitable as indices of self.
If subname is not None, restrict the keys to those with that subname.
Entities.set_as_zone(self, zone: str, if_unset=False)
Set this Entities instance as the one handling entities in zone.
Entities.zone_entity(self, zone: str) -> 'Entity'
Return the Entity entity associated with a per-type-zone key.
For example, self.zone_entity('tvdb') would return the entity
for tvdb.tvdb_id where tvdb_id comes from self['id.tvdb'].
class Entity(cs.tagset.ZonedTypes, cs.obj.Refreshable, cs.deco.Promotable, cs.lex.FormatableMixin, cs.obj.NoAttrs)
A base class for classes which have a .tags:TagSet attribute
and a .tags_db:BaseTagSets containing other Tagsets.
Usually these are considered part of a "zone" - a group of entities in a particular applicaiton domain.
The subclass may itself define its .tags instance attribute
or rely on the default cached property .tags, which will return
self.tags_db[self.tags_entity_key].
(self.tags_entity_key is self.tags.name by default.)
Note that this mixin brings its own __new__ method which
can choose a subclass based on the subclass' .TYPE_SUBNAME
attribute. See the __new__ docstring.
This also provides some behaviour based around updating
entities based on some kind of API call; the direct values
from the API call land on attributes named {zone}.{key} -
the .type_zone_update(mapping) provides a convenient call
for this.
Entity instances are designed as representing entities in
some "zone", a set of entities in some domain or organised
grouping; typical examples include entities describes by some
API like MusicBrainzNG or objects presented by some website.
As such, they subclass ZonedTypes, which expects the entity's
.name to be of the form zone.subname.key; the
zone partitions entities off into their own domain, the
subname is in effect the entity's type within that domain
and the key is the entity id within that type.
On this basis, entities updated with data from the zone,
for example from an API call or by scraping a web page,
normally update tag keys named zone.field where the field
is the top level field from the data.
The ZonedTypes.__getattr__(attr) method looks first for a
direct tag named attr but falls back to a tag named
zone.attr. This allows entities to be tagged with the
data from an API, but to be overridden by the direct tag if
the API data are considered incorrect or unsuitable.
The ScanData.apply() method follows this principle,
applying the scanned data to tags named zone.field.
We relate entities using attributes named field_id,
which may be a single key for another entity or a list of keys.
Various derived attributes are also provided, see the
__getattr__ docstring for details:
- field
_ent: the relatedEntitynamed zone.field.key where key comes from the.field_idattribute - field
_ents: multiple relatedEntitynamed zone.field.key where key comes from the.field_idattribute
Entity.__annotations__
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
Entity.__delitem__(self, tag_name: str)
Remove an entry from self.tags.
Entity.__firstlineno__
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
int('0b100', base=0) 4
Entity.__getattr__(self, attr)
Try ZonedTypes.__getattr__ (which lokks up [attr] then [f'{zone}.{attr}'])
then fall back to suffix based synthetic attributes where
an attribute ending in _suffix is implemented by the
suffix_suffix(attr) method if it exists.
The following synthetic attibutes are implemented:
- attr0
_or_none: return.attr0orNoneif that does not exist - subtype
_ent: the entity with name type_zone.subtype.id orNonewhereidcomes from the.attr_idvalue; see thesuffix_entmethod. - subtype
_ents: the entities with name type_zone.subtype.id orNonewhere eachidcomes from the.attr_idvalues; see thesuffix_entsmethod.
Entity.__getitem__(self, tag_name: str)
Index self.tags.
Entity.__setitem__(self, tag_name, value, *, verbose=False)
Set a tag value.
Entity.__static_attributes__
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
Entity.as_dict(self)
Proxy .as_dict() to self.tags.
Entity.entity
The .entity attribute space, whose attributes map to
entities which are UsesTags instances from the appropriate
Entities instances according to their zone.
Example:
tags = TagSet({'id.playon':'recording.1234567'})
playon_recording = tags.entity.playon
Entity.entity_
The .entity_ attribute space, whose attributes map to
entities which are UsesTags instances from the appropriate
Entities instances according to their zone.
Unlike .entity, a missing id. tag returns None instead
of raising AttributeError.
Example:
tags = TagSet({'id.playon':'recording.1234567'})
playon_recording = tags.entity.playon
Entity.format_attributes
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
Entity.format_kwargs(self)
A format_kwargs method to support cs.lex.FormatableMixin.
Entity.get(self, tag_name: str, default=None)
Call .tags.get(tag_name).
Entity.items(self)
The tags items.
Entity.prefix_in(self, attr) -> collections.abc.Sequence[typing.Self]
Resolve in_subtype[_field to the Entity instance
of subtype subtype whose field_id attribute contains
self.type_key.
The default field is self.type_subname.
For example, if self.name is "tvdb.episode.1234" then
self.in_season would return a list of all the tvdb.season
entities whose episode_id attributes referred to 1234.
Where the
Entity.print(self)
The default print() runs self.printt().
This is intended to be a nice print of important stuff.
Entity.refresh_key(self)
The unique key identifying this object for use in recursive refreshes.
Entity.refresh_last_update
The last time a refresh update time.
Entity.setdefault(self, key, default_value)
Set self[key]=default_value if key is not present.
Entity.suffix_ent(self, attr) -> Optional[Self]
Resolve subtype_ent to self[type_zone.subtype.id]
or None if no self[subtype_id]
Entity.suffix_ents(self, attr) -> collections.abc.Sequence[typing.Self]
Resolve subtype_ents to [self[type_zone.*subtype*.id]]or()if noself[*subtype*_id]]`.
Entity.tags
A default .tags property which obtains a TagSet from self.tags_db
via using the TagSet name self.tags_entity_key.
This is for subclasses which might fetch the .tags on demand.
Subclasses typically set .tags during __init__ and
therefore have no need for a .tags_entity_key property.
Entity.tags_entity_key
Our tagged entity key, self.tags.name.
This is only really needed by the .tags cached
property; most subclasses of Entity set .tags during
__init__.
If you have an "on demand" subclass you should override
this method to compute the entity key without relying on
the (missing) .tags attribute.
Entity.type_zone_update(self, mapping, prefix=None, *, lc_=False)
Update self with mapping, using prefix.
The default prefix is self.type_zone`.
Entity.update(self, *update_a, **update_kw)
Update the tags, tupically from a mapping or keyword arguments.
Entity.values(self)
The tags values.
class ScanData
A class to manage data obtained about SiteEntity instances,
for example from an API or scanning a web page.
The data for an SiteEntity can be obtained by indexing the
ScanData instance with a SiteEntity instance or
a (ent_cls,type_key) 2-tuple
ScanData.__dict__
Read-only proxy of a mapping.
ScanData.__firstlineno__
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
int('0b100', base=0) 4
ScanData.__getitem__(self, ent: Union[tuple, ForwardRef('Entity')])
The data for the supplied ent.
ScanData.__iter__(self)
Iteration yields (Entity,datadict) 2-tuples.
ScanData.__static_attributes__
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
ScanData.apply(self, *refresh_ents)
Apply the scanned data to its entities.
If an entity ent is a member of refresh_ents then call
ent.refresh(data=data) on the basis that the data are
complete enough to consider the entity refreshed, otherwise
call ent.type_zone_update(data).
The purpose of the call to ent.refresh() is to exercise
the refresh machinery. On a Refreshable object ent this
marks the object as current with the new data; the data are
applied with Refreshable._refresh(), the zone specific
method, which typically also uses ent.type_zone_update(data).
This follows the tag name design outlined in the Entity docstring,
where API/site data are stored with tags named zone.field.
ScanData.conv(self, ent: Union[tuple, ForwardRef('Entity')], mapping, key, conv=None)
Update the data for ent from mapping[key] if present.
If conv is not None it should be a callable accepting
the value from mapping[key] and returning a converted
value to store in the entity data.
ScanData.printt(self, title=None)
Call cs.lex.printt() to print the scanned data.
ScanData.update(self, ent: Union[tuple[type, int | str], ForwardRef('Entity')], **data_kw)
Update the data for ent from data_kw.
Release Log
Release 20260912: First PyPI release: Entity, Entities, ScanData.
Release files for cs-tagged 20260912
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cs_tagged-20260912.tar.gz | 18.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cs_tagged-20260912-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.9 kB
Release files / cs_tagged-20260912.tar.gz
| Download URL | cs_tagged-20260912.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
06dfa739136815c50c30a70546b1dd768a97af961d7e21ab6bd419634b180b7f
|
|
BLAKE2b-256 checksum How to use checksums |
fa2ed382aec7d84fdcbcb111e4ea6a61f227f80987d360c163f1171325d81883
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.1
|
Release files / cs_tagged-20260912-py3-none-any.whl
| Download URL | cs_tagged-20260912-py3-none-any.whl |
|---|---|
| Size | 17.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c8498f5f5e9b0ddcdccb09ee8268105c65a8ff5f27aef94a9ab0a5a96859fce
|
|
BLAKE2b-256 checksum How to use checksums |
9718e5676cf9cd44a4f797362286b10455986ec544787bdf062bb9a3939600e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.1
|