followthemoney 1.3.6__py3-none-any.whl → 3.8.0__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.
- followthemoney/__init__.py +5 -3
- followthemoney/cli/__init__.py +17 -0
- followthemoney/cli/aggregate.py +56 -0
- followthemoney/cli/cli.py +88 -0
- followthemoney/cli/exports.py +121 -0
- followthemoney/cli/mapping.py +85 -0
- followthemoney/cli/sieve.py +67 -0
- followthemoney/cli/util.py +142 -0
- followthemoney/compare.py +132 -55
- followthemoney/exc.py +19 -6
- followthemoney/export/common.py +29 -0
- followthemoney/export/csv.py +82 -0
- followthemoney/export/excel.py +75 -0
- followthemoney/export/graph.py +79 -0
- followthemoney/export/neo4j.py +182 -0
- followthemoney/export/rdf.py +26 -0
- followthemoney/graph.py +308 -0
- followthemoney/helpers.py +212 -0
- followthemoney/mapping/__init__.py +1 -1
- followthemoney/mapping/csv.py +67 -35
- followthemoney/mapping/entity.py +116 -44
- followthemoney/mapping/property.py +90 -44
- followthemoney/mapping/query.py +27 -19
- followthemoney/mapping/source.py +15 -5
- followthemoney/mapping/sql.py +75 -61
- followthemoney/messages.py +13 -7
- followthemoney/model.py +108 -56
- followthemoney/namespace.py +119 -0
- followthemoney/offshore.py +48 -0
- followthemoney/ontology.py +77 -0
- followthemoney/property.py +204 -71
- followthemoney/proxy.py +455 -118
- followthemoney/rdf.py +9 -0
- followthemoney/schema/Address.yaml +78 -0
- followthemoney/schema/Airplane.yaml +17 -10
- followthemoney/schema/Analyzable.yaml +54 -0
- followthemoney/schema/Article.yaml +16 -0
- followthemoney/schema/Assessment.yaml +32 -0
- followthemoney/schema/Asset.yaml +10 -4
- followthemoney/schema/Associate.yaml +41 -0
- followthemoney/schema/Audio.yaml +24 -0
- followthemoney/schema/BankAccount.yaml +53 -9
- followthemoney/schema/Call.yaml +48 -0
- followthemoney/schema/CallForTenders.yaml +117 -0
- followthemoney/schema/Company.yaml +37 -12
- followthemoney/schema/Contract.yaml +41 -7
- followthemoney/schema/ContractAward.yaml +30 -11
- followthemoney/schema/CourtCase.yaml +16 -10
- followthemoney/schema/CourtCaseParty.yaml +17 -6
- followthemoney/schema/CryptoWallet.yaml +48 -0
- followthemoney/schema/Debt.yaml +37 -0
- followthemoney/schema/Directorship.yaml +17 -4
- followthemoney/schema/Document.yaml +72 -139
- followthemoney/schema/Documentation.yml +38 -0
- followthemoney/schema/EconomicActivity.yaml +32 -17
- followthemoney/schema/Email.yaml +76 -0
- followthemoney/schema/Employment.yaml +39 -0
- followthemoney/schema/Event.yaml +35 -3
- followthemoney/schema/Family.yaml +41 -0
- followthemoney/schema/Folder.yaml +13 -0
- followthemoney/schema/HyperText.yaml +21 -0
- followthemoney/schema/Identification.yaml +40 -0
- followthemoney/schema/Image.yaml +25 -0
- followthemoney/schema/Interest.yaml +3 -6
- followthemoney/schema/Interval.yaml +56 -5
- followthemoney/schema/LegalEntity.yaml +81 -20
- followthemoney/schema/License.yaml +7 -3
- followthemoney/schema/Membership.yaml +19 -4
- followthemoney/schema/Mention.yaml +54 -0
- followthemoney/schema/Message.yaml +73 -0
- followthemoney/schema/Note.yaml +23 -0
- followthemoney/schema/Occupancy.yaml +40 -0
- followthemoney/schema/Organization.yaml +38 -3
- followthemoney/schema/Ownership.yaml +16 -4
- followthemoney/schema/Package.yaml +17 -0
- followthemoney/schema/Page.yaml +43 -0
- followthemoney/schema/Pages.yaml +23 -0
- followthemoney/schema/Passport.yaml +15 -17
- followthemoney/schema/Payment.yaml +38 -7
- followthemoney/schema/Person.yaml +61 -5
- followthemoney/schema/PlainText.yaml +17 -0
- followthemoney/schema/Position.yaml +50 -0
- followthemoney/schema/Post.yaml +42 -0
- followthemoney/schema/Project.yaml +27 -0
- followthemoney/schema/ProjectParticipant.yaml +36 -0
- followthemoney/schema/PublicBody.yaml +14 -3
- followthemoney/schema/RealEstate.yaml +19 -3
- followthemoney/schema/Representation.yaml +17 -6
- followthemoney/schema/Sanction.yaml +44 -20
- followthemoney/schema/Security.yaml +59 -0
- followthemoney/schema/Similar.yaml +37 -0
- followthemoney/schema/Succession.yaml +36 -0
- followthemoney/schema/Table.yaml +32 -0
- followthemoney/schema/TaxRoll.yaml +27 -9
- followthemoney/schema/Thing.yaml +69 -13
- followthemoney/schema/Trip.yaml +42 -0
- followthemoney/schema/UnknownLink.yaml +17 -6
- followthemoney/schema/UserAccount.yaml +44 -0
- followthemoney/schema/Value.yaml +5 -1
- followthemoney/schema/Vehicle.yaml +25 -8
- followthemoney/schema/Vessel.yaml +18 -10
- followthemoney/schema/Video.yaml +20 -0
- followthemoney/schema/Workbook.yaml +18 -0
- followthemoney/schema.py +406 -135
- followthemoney/translations/ar/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/ar/LC_MESSAGES/followthemoney.po +2900 -787
- followthemoney/translations/bs/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/bs/LC_MESSAGES/followthemoney.po +2108 -520
- followthemoney/translations/de/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/de/LC_MESSAGES/followthemoney.po +2902 -782
- followthemoney/translations/es/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/es/LC_MESSAGES/followthemoney.po +2893 -779
- followthemoney/translations/fr/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/fr/LC_MESSAGES/followthemoney.po +4362 -0
- followthemoney/translations/fr/followthemoney.po +3861 -0
- followthemoney/translations/messages.pot +3021 -725
- followthemoney/translations/nb/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/nb/LC_MESSAGES/followthemoney.po +3778 -0
- followthemoney/translations/nl/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/nl/LC_MESSAGES/followthemoney.po +3837 -0
- followthemoney/translations/pt_BR/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/pt_BR/LC_MESSAGES/followthemoney.po +3784 -0
- followthemoney/translations/ru/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/ru/LC_MESSAGES/followthemoney.po +2837 -539
- followthemoney/translations/ru/followthemoney.po +4221 -0
- followthemoney/translations/tr/LC_MESSAGES/followthemoney.mo +0 -0
- followthemoney/translations/tr/LC_MESSAGES/followthemoney.po +2073 -491
- followthemoney/types/__init__.py +35 -17
- followthemoney/types/address.py +41 -21
- followthemoney/types/checksum.py +25 -0
- followthemoney/types/common.py +233 -88
- followthemoney/types/country.py +89 -56
- followthemoney/types/date.py +59 -76
- followthemoney/types/email.py +66 -35
- followthemoney/types/entity.py +66 -13
- followthemoney/types/gender.py +66 -0
- followthemoney/types/iban.py +47 -28
- followthemoney/types/identifier.py +49 -22
- followthemoney/types/ip.py +35 -21
- followthemoney/types/json.py +58 -0
- followthemoney/types/language.py +124 -37
- followthemoney/types/mimetype.py +44 -0
- followthemoney/types/name.py +56 -12
- followthemoney/types/number.py +30 -0
- followthemoney/types/phone.py +92 -34
- followthemoney/types/registry.py +52 -0
- followthemoney/types/string.py +43 -0
- followthemoney/types/topic.py +94 -0
- followthemoney/types/url.py +39 -17
- followthemoney/util.py +139 -45
- followthemoney-3.8.0.dist-info/METADATA +153 -0
- followthemoney-3.8.0.dist-info/RECORD +157 -0
- {followthemoney-1.3.6.dist-info → followthemoney-3.8.0.dist-info}/WHEEL +1 -2
- followthemoney-3.8.0.dist-info/entry_points.txt +17 -0
- followthemoney-1.3.6.dist-info/LICENSE.txt → followthemoney-3.8.0.dist-info/licenses/LICENSE +1 -1
- followthemoney/link.py +0 -75
- followthemoney/schema/Associate.yml +0 -19
- followthemoney/schema/Family.yml +0 -19
- followthemoney/schema/Land.yml +0 -9
- followthemoney/schema/Relationship.yaml +0 -26
- followthemoney/types/domain.py +0 -50
- followthemoney-1.3.6.dist-info/DESCRIPTION.rst +0 -3
- followthemoney-1.3.6.dist-info/METADATA +0 -39
- followthemoney-1.3.6.dist-info/RECORD +0 -108
- followthemoney-1.3.6.dist-info/entry_points.txt +0 -3
- followthemoney-1.3.6.dist-info/metadata.json +0 -1
- followthemoney-1.3.6.dist-info/namespace_packages.txt +0 -1
- followthemoney-1.3.6.dist-info/top_level.txt +0 -3
- ns/ontology.py +0 -128
- tests/types/test_addresses.py +0 -24
- tests/types/test_common.py +0 -27
- tests/types/test_countries.py +0 -21
- tests/types/test_dates.py +0 -72
- tests/types/test_domains.py +0 -23
- tests/types/test_emails.py +0 -30
- tests/types/test_entity.py +0 -16
- tests/types/test_iban.py +0 -109
- tests/types/test_identifiers.py +0 -25
- tests/types/test_ip.py +0 -26
- tests/types/test_languages.py +0 -20
- tests/types/test_names.py +0 -33
- tests/types/test_phones.py +0 -24
- tests/types/test_registry.py +0 -14
- tests/types/test_urls.py +0 -23
- {ns → followthemoney/export}/__init__.py +0 -0
- /tests/types/__init__.py → /followthemoney/py.typed +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from rdflib import Graph, URIRef, Literal
|
|
4
|
+
from rdflib.namespace import OWL, DCTERMS, RDF, RDFS, XSD
|
|
5
|
+
|
|
6
|
+
from followthemoney import model
|
|
7
|
+
from followthemoney.property import Property
|
|
8
|
+
from followthemoney.schema import Schema
|
|
9
|
+
from followthemoney.types import registry
|
|
10
|
+
from followthemoney.rdf import NS
|
|
11
|
+
from followthemoney.util import PathLike
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Ontology(object):
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self.uri = URIRef(NS)
|
|
17
|
+
self.graph = Graph(identifier=self.uri)
|
|
18
|
+
self.graph.namespace_manager.bind("ftm", NS)
|
|
19
|
+
self.graph.namespace_manager.bind("owl", OWL)
|
|
20
|
+
self.graph.namespace_manager.bind("dct", DCTERMS)
|
|
21
|
+
|
|
22
|
+
self.graph.add((self.uri, RDF.type, OWL.Ontology))
|
|
23
|
+
self.graph.add((self.uri, RDFS.label, Literal("Follow The Money")))
|
|
24
|
+
modified = datetime.now().strftime("%Y-%m-%dT%H:%I:%M")
|
|
25
|
+
modified = Literal(modified, datatype=XSD.dateTime)
|
|
26
|
+
self.graph.add((self.uri, DCTERMS.modified, modified))
|
|
27
|
+
|
|
28
|
+
self.add_schemata()
|
|
29
|
+
|
|
30
|
+
def add_schemata(self) -> None:
|
|
31
|
+
for schema in sorted(model):
|
|
32
|
+
self.add_class(schema)
|
|
33
|
+
|
|
34
|
+
def add_class(self, schema: Schema) -> None:
|
|
35
|
+
self.graph.add((schema.uri, RDF.type, RDFS.Class))
|
|
36
|
+
self.graph.add((schema.uri, RDFS.isDefinedBy, self.uri))
|
|
37
|
+
for parent in schema.extends:
|
|
38
|
+
self.graph.add((schema.uri, RDFS.subClassOf, parent.uri))
|
|
39
|
+
|
|
40
|
+
self.graph.add((schema.uri, RDFS.label, Literal(schema.label)))
|
|
41
|
+
if schema.description is not None:
|
|
42
|
+
description = Literal(schema.description)
|
|
43
|
+
self.graph.add((schema.uri, RDFS.comment, description))
|
|
44
|
+
|
|
45
|
+
for _, prop in sorted(schema.properties.items()):
|
|
46
|
+
self.add_property(prop)
|
|
47
|
+
|
|
48
|
+
def add_property(self, prop: Property) -> None:
|
|
49
|
+
self.graph.add((prop.uri, RDF.type, RDF.Property))
|
|
50
|
+
self.graph.add((prop.uri, RDFS.isDefinedBy, self.uri))
|
|
51
|
+
|
|
52
|
+
self.graph.add((prop.uri, RDFS.label, Literal(prop.label)))
|
|
53
|
+
if prop.description is not None:
|
|
54
|
+
self.graph.add((prop.uri, RDFS.comment, Literal(prop.description)))
|
|
55
|
+
|
|
56
|
+
self.graph.add((prop.uri, RDFS.domain, prop.schema.uri))
|
|
57
|
+
if prop.range is not None:
|
|
58
|
+
range = model.get(prop.range)
|
|
59
|
+
if range is not None:
|
|
60
|
+
range_uri = range.uri
|
|
61
|
+
self.graph.add((prop.uri, RDFS.range, range_uri))
|
|
62
|
+
if prop.reverse is not None:
|
|
63
|
+
self.graph.add((prop.uri, OWL.inverseOf, prop.reverse.uri))
|
|
64
|
+
if prop.type == registry.date:
|
|
65
|
+
self.graph.add((prop.uri, RDFS.range, XSD.dateTime))
|
|
66
|
+
|
|
67
|
+
def write_namespace_docs(self, path: PathLike) -> None:
|
|
68
|
+
xml_fn = "%s/ftm.xml" % path
|
|
69
|
+
with open(xml_fn, "w") as xml_file:
|
|
70
|
+
xml_file.write(self.graph.serialize(format="xml"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
path = sys.argv[1]
|
|
75
|
+
ontology = Ontology()
|
|
76
|
+
ontology.write_namespace_docs(path)
|
|
77
|
+
print("Namespace docs written to %s" % path)
|
followthemoney/property.py
CHANGED
|
@@ -1,96 +1,229 @@
|
|
|
1
|
-
from banal import
|
|
2
|
-
from
|
|
1
|
+
from banal import is_mapping, as_bool
|
|
2
|
+
from typing import TYPE_CHECKING, cast, Any, List, Optional, TypedDict
|
|
3
3
|
|
|
4
4
|
from followthemoney.exc import InvalidModel
|
|
5
5
|
from followthemoney.types import registry
|
|
6
|
-
from followthemoney.
|
|
6
|
+
from followthemoney.rdf import NS, URIRef
|
|
7
|
+
from followthemoney.util import gettext, get_entity_id
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from followthemoney.schema import Schema
|
|
11
|
+
from followthemoney.model import Model
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReverseSpec(TypedDict, total=False):
|
|
15
|
+
name: str
|
|
16
|
+
label: Optional[str]
|
|
17
|
+
hidden: Optional[bool]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PropertyDict(TypedDict, total=False):
|
|
21
|
+
label: Optional[str]
|
|
22
|
+
description: Optional[str]
|
|
23
|
+
type: Optional[str]
|
|
24
|
+
hidden: Optional[bool]
|
|
25
|
+
matchable: Optional[bool]
|
|
26
|
+
deprecated: Optional[bool]
|
|
27
|
+
maxLength: Optional[int]
|
|
28
|
+
# stub: Optional[bool]
|
|
29
|
+
rdf: Optional[str]
|
|
30
|
+
range: Optional[str]
|
|
31
|
+
format: Optional[str]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PropertySpec(PropertyDict):
|
|
35
|
+
reverse: ReverseSpec
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PropertyToDict(PropertyDict, total=False):
|
|
39
|
+
name: str
|
|
40
|
+
qname: str
|
|
41
|
+
reverse: Optional[str]
|
|
42
|
+
stub: Optional[bool]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Property:
|
|
46
|
+
"""A definition of a value-holding field on a schema. Properties define
|
|
47
|
+
the field type and other possible constraints. They also serve as entity
|
|
48
|
+
to entity references."""
|
|
49
|
+
|
|
50
|
+
__slots__ = (
|
|
51
|
+
"model",
|
|
52
|
+
"schema",
|
|
53
|
+
"name",
|
|
54
|
+
"qname",
|
|
55
|
+
"_label",
|
|
56
|
+
"_hash",
|
|
57
|
+
"_description",
|
|
58
|
+
"hidden",
|
|
59
|
+
"type",
|
|
60
|
+
"matchable",
|
|
61
|
+
"deprecated",
|
|
62
|
+
"max_length",
|
|
63
|
+
"_range",
|
|
64
|
+
"format",
|
|
65
|
+
"range",
|
|
66
|
+
"stub",
|
|
67
|
+
"_reverse",
|
|
68
|
+
"reverse",
|
|
69
|
+
"uri",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
#: Invalid property names.
|
|
73
|
+
RESERVED = ["id", "caption", "schema", "schemata"]
|
|
74
|
+
|
|
75
|
+
def __init__(self, schema: "Schema", name: str, data: PropertySpec) -> None:
|
|
76
|
+
#: The schema which the property is defined for. This is always the
|
|
77
|
+
#: most abstract schema that has this property, not the possible
|
|
78
|
+
#: child schemata that inherit it.
|
|
79
|
+
self.schema = schema
|
|
7
80
|
|
|
81
|
+
#: Machine-readable name for this property.
|
|
82
|
+
self.name = name
|
|
8
83
|
|
|
9
|
-
|
|
84
|
+
#: Qualified property name, which also includes the schema name.
|
|
85
|
+
self.qname = "%s:%s" % (schema.name, self.name)
|
|
86
|
+
if self.name in self.RESERVED:
|
|
87
|
+
raise InvalidModel("Reserved name: %s" % self.name)
|
|
10
88
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
self.
|
|
14
|
-
self.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
self.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
self.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
self.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
89
|
+
self._hash = hash("<Property(%r)>" % self.qname)
|
|
90
|
+
|
|
91
|
+
self._label = data.get("label", name)
|
|
92
|
+
self._description = data.get("description")
|
|
93
|
+
|
|
94
|
+
#: This property is deprecated and should not be used.
|
|
95
|
+
self.deprecated = as_bool(data.get("deprecated", False))
|
|
96
|
+
|
|
97
|
+
#: This property should not be shown or mentioned in the user interface.
|
|
98
|
+
self.hidden = as_bool(data.get("hidden"))
|
|
99
|
+
|
|
100
|
+
type_ = data.get("type", "string")
|
|
101
|
+
if type_ is None or type_ not in registry.named:
|
|
102
|
+
raise InvalidModel("Invalid type: %s" % type_)
|
|
103
|
+
|
|
104
|
+
#: The data type for this property.
|
|
105
|
+
self.type = registry[type_]
|
|
106
|
+
|
|
107
|
+
#: Whether this property should be used for matching and cross-referencing.
|
|
108
|
+
_matchable = data.get("matchable")
|
|
109
|
+
if _matchable is not None:
|
|
110
|
+
self.matchable = as_bool(data.get("matchable"))
|
|
111
|
+
else:
|
|
112
|
+
self.matchable = self.type.matchable
|
|
113
|
+
|
|
114
|
+
#: The maximum length of the property value.
|
|
115
|
+
self.max_length = int(data.get("maxLength") or self.type.max_length)
|
|
116
|
+
|
|
117
|
+
#: If the property is of type ``entity``, the set of valid schema to be added
|
|
118
|
+
#: in this property can be constrained. For example, an asset can be owned,
|
|
119
|
+
#: but a person cannot be owned.
|
|
120
|
+
self._range = data.get("range")
|
|
121
|
+
self.range: Optional["Schema"] = None
|
|
122
|
+
|
|
123
|
+
#: If the property is of type ``identifier``, a more narrow definition of the
|
|
124
|
+
#: identifier format can be provided. For example, LEI, INN or IBAN codes
|
|
125
|
+
#: can be automatically validated.
|
|
126
|
+
self.format: Optional[str] = data.get("format")
|
|
127
|
+
|
|
128
|
+
#: When a property points to another schema, a reverse property is added for
|
|
129
|
+
#: various administrative reasons. These properties are, however, not real
|
|
130
|
+
#: and cannot be written to. That's why they are marked as stubs and adding
|
|
131
|
+
#: values to them will raise an exception.
|
|
132
|
+
self.stub: Optional[bool] = False
|
|
133
|
+
|
|
134
|
+
#: When a property points to another schema, a stub reverse property is
|
|
135
|
+
#: added as a place to store metadata to help display the link in inverted
|
|
136
|
+
#: views.
|
|
137
|
+
self._reverse = data.get("reverse")
|
|
138
|
+
self.reverse: Optional["Property"] = None
|
|
139
|
+
|
|
140
|
+
#: RDF term for this property (i.e. the predicate URI).
|
|
141
|
+
self.uri = URIRef(cast(str, data.get("rdf", NS[self.qname])))
|
|
142
|
+
|
|
143
|
+
def generate(self, model: "Model") -> None:
|
|
144
|
+
"""Setup method used when loading the model in order to build out the reverse
|
|
145
|
+
links of the property."""
|
|
146
|
+
model.properties.add(self)
|
|
147
|
+
|
|
148
|
+
if self.type == registry.entity:
|
|
149
|
+
if self.range is None and self._range is not None:
|
|
150
|
+
self.range = model.get(self._range)
|
|
151
|
+
|
|
152
|
+
if self.reverse is None and self.range and self._reverse:
|
|
153
|
+
if not is_mapping(self._reverse):
|
|
154
|
+
raise InvalidModel("Invalid reverse: %s" % self)
|
|
155
|
+
self.reverse = self.range._add_reverse(model, self._reverse, self)
|
|
45
156
|
|
|
46
157
|
@property
|
|
47
|
-
def label(self):
|
|
158
|
+
def label(self) -> str:
|
|
159
|
+
"""User-facing title for this property."""
|
|
48
160
|
return gettext(self._label)
|
|
49
161
|
|
|
50
162
|
@property
|
|
51
|
-
def description(self):
|
|
163
|
+
def description(self) -> str:
|
|
164
|
+
"""A longer description of the semantics of this property."""
|
|
52
165
|
return gettext(self._description)
|
|
53
166
|
|
|
54
|
-
def
|
|
167
|
+
def specificity(self, value: str) -> float:
|
|
168
|
+
"""Return a measure of how precise the given value is."""
|
|
169
|
+
if not self.matchable:
|
|
170
|
+
return 0.0
|
|
171
|
+
return self.type.specificity(value)
|
|
172
|
+
|
|
173
|
+
def validate(self, data: List[Any]) -> Optional[str]:
|
|
55
174
|
"""Validate that the data should be stored.
|
|
56
175
|
|
|
57
176
|
Since the types system doesn't really have validation, this currently
|
|
58
177
|
tries to normalize the value to see if it passes strict parsing.
|
|
59
178
|
"""
|
|
60
179
|
values = []
|
|
61
|
-
for val in
|
|
62
|
-
if
|
|
63
|
-
|
|
180
|
+
for val in data:
|
|
181
|
+
if self.stub:
|
|
182
|
+
return gettext("Property cannot be written")
|
|
183
|
+
val = get_entity_id(val)
|
|
184
|
+
if val is None:
|
|
185
|
+
continue
|
|
64
186
|
if not self.type.validate(val):
|
|
65
|
-
return gettext(
|
|
187
|
+
return gettext("Invalid value")
|
|
66
188
|
if val is not None:
|
|
67
189
|
values.append(val)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
data = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'uri': str(self.uri),
|
|
85
|
-
'type': self._type
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
def __eq__(self, other: Any) -> bool:
|
|
193
|
+
return self._hash == hash(other)
|
|
194
|
+
|
|
195
|
+
def __hash__(self) -> int:
|
|
196
|
+
return self._hash
|
|
197
|
+
|
|
198
|
+
def to_dict(self) -> PropertyToDict:
|
|
199
|
+
"""Return property metadata in a serializable form."""
|
|
200
|
+
data: PropertyToDict = {
|
|
201
|
+
"name": self.name,
|
|
202
|
+
"qname": self.qname,
|
|
203
|
+
"label": self.label,
|
|
204
|
+
"type": self.type.name,
|
|
205
|
+
"maxLength": self.max_length,
|
|
86
206
|
}
|
|
87
|
-
if self.
|
|
88
|
-
data[
|
|
89
|
-
if self.
|
|
90
|
-
data[
|
|
91
|
-
if self.
|
|
92
|
-
data[
|
|
207
|
+
if self.description:
|
|
208
|
+
data["description"] = self.description
|
|
209
|
+
if self.stub:
|
|
210
|
+
data["stub"] = True
|
|
211
|
+
if self.matchable:
|
|
212
|
+
data["matchable"] = True
|
|
213
|
+
if self.hidden:
|
|
214
|
+
data["hidden"] = True
|
|
215
|
+
if self.deprecated:
|
|
216
|
+
data["deprecated"] = True
|
|
217
|
+
if self.range is not None:
|
|
218
|
+
data["range"] = self.range.name
|
|
219
|
+
if self.reverse is not None:
|
|
220
|
+
data["reverse"] = self.reverse.name
|
|
221
|
+
if self.format is not None:
|
|
222
|
+
data["format"] = self.format
|
|
93
223
|
return data
|
|
94
224
|
|
|
95
|
-
def __repr__(self):
|
|
96
|
-
return
|
|
225
|
+
def __repr__(self) -> str:
|
|
226
|
+
return "<Property(%r)>" % self.qname
|
|
227
|
+
|
|
228
|
+
def __str__(self) -> str:
|
|
229
|
+
return self.qname
|