followthemoney 4.2.0__py3-none-any.whl → 4.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of followthemoney might be problematic. Click here for more details.

@@ -9,7 +9,7 @@ from followthemoney.statement import Statement, StatementEntity, SE
9
9
  from followthemoney.dataset import Dataset, DefaultDataset, DS
10
10
  from followthemoney.util import set_model_locale
11
11
 
12
- __version__ = "4.2.0"
12
+ __version__ = "4.2.2"
13
13
 
14
14
  # Data model singleton
15
15
  model = Model.instance()
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from banal import is_mapping, as_bool
2
3
  from typing import TYPE_CHECKING, Any, List, Optional, TypedDict
3
4
 
@@ -9,6 +10,18 @@ if TYPE_CHECKING:
9
10
  from followthemoney.schema import Schema
10
11
  from followthemoney.model import Model
11
12
 
13
+ # Invalid property names.
14
+ RESERVED = ["id", "caption", "schema", "schemata", "referents", "datasets"]
15
+ PROP_NAME_RE = re.compile("^[a-z][a-zA-Z0-9]*$")
16
+
17
+
18
+ def check_property_name(name: str) -> bool:
19
+ if name in RESERVED:
20
+ return False
21
+ if not PROP_NAME_RE.match(name):
22
+ return False
23
+ return True
24
+
12
25
 
13
26
  class ReverseSpec(TypedDict, total=False):
14
27
  name: str
@@ -66,9 +79,6 @@ class Property:
66
79
  "reverse",
67
80
  )
68
81
 
69
- #: Invalid property names.
70
- RESERVED = ["id", "caption", "schema", "schemata"]
71
-
72
82
  def __init__(self, schema: "Schema", name: str, data: PropertySpec) -> None:
73
83
  #: The schema which the property is defined for. This is always the
74
84
  #: most abstract schema that has this property, not the possible
@@ -77,11 +87,11 @@ class Property:
77
87
 
78
88
  #: Machine-readable name for this property.
79
89
  self.name = const(name)
90
+ if not check_property_name(self.name):
91
+ raise InvalidModel("Invalid name: %s" % self.name)
80
92
 
81
93
  #: Qualified property name, which also includes the schema name.
82
94
  self.qname = const("%s:%s" % (schema.name, self.name))
83
- if self.name in self.RESERVED:
84
- raise InvalidModel("Reserved name: %s" % self.name)
85
95
 
86
96
  self._hash = hash("<Property(%r)>" % self.qname)
87
97
 
@@ -58,20 +58,20 @@ Company:
58
58
  type: identifier
59
59
  caemCode:
60
60
  label: "COD CAEM"
61
- description: "(RO) What kind of activity a legal entity is allowed to develop"
61
+ description: "Romanian classifier used to identify the types of economic activities that a business can provide in Romania"
62
62
  matchable: false
63
63
  kppCode:
64
64
  label: "KPP"
65
- description: "(RU, КПП) in addition to INN for orgs; reason for registration at FNS"
65
+ description: "Russian code issued by the tax authority, identifies the reason for registration at the Federal Tax Service (Russian: КПП). A company may have multiple KPP codes (e.g. for different branches), and the codes are not unique across companies."
66
66
  type: identifier
67
67
  matchable: false
68
68
  okvedCode:
69
69
  label: "OKVED(2) Classifier"
70
- description: "(RU, ОКВЭД) Economical activity classifier. OKVED2 is the same but newer"
70
+ description: "Russian classifier that that categorizes businesses by their (primary and secondary) economic activities (Russian: ОКВЭД)"
71
71
  matchable: false
72
72
  okopfCode:
73
73
  label: "OKOPF"
74
- description: "(RU, ОКОПФ) What kind of business entity"
74
+ description: "Russian classifier that that categorizes different types of legal entities in Russia based on their organizational and legal structure (Russian: ОКОПФ)"
75
75
  matchable: false
76
76
  fnsCode:
77
77
  label: "Federal tax service code"
@@ -74,7 +74,7 @@ LegalEntity:
74
74
  idNumber:
75
75
  label: ID Number
76
76
  type: identifier
77
- description: "ID number of any applicable ID"
77
+ description: "ID of any applicable personal identification document. Used mainly for people and their national ID cards."
78
78
  taxNumber:
79
79
  label: Tax Number
80
80
  type: identifier
@@ -118,13 +118,13 @@ LegalEntity:
118
118
  matchable: false
119
119
  innCode:
120
120
  label: "INN"
121
- description: "Russian company ID"
121
+ description: "Russian tax identification number (Russian: ИНН). Issued to businesses and individuals in Russia"
122
122
  type: identifier
123
123
  format: inn
124
124
  maxLength: 32
125
125
  ogrnCode:
126
126
  label: "OGRN"
127
- description: "Major State Registration Number"
127
+ description: "Identification number used in Russia's Unified State Register of Legal Entities (EGRUL) (Russian: ОГРН)"
128
128
  type: identifier
129
129
  format: ogrn
130
130
  maxLength: 32
@@ -148,7 +148,7 @@ LegalEntity:
148
148
  maxLength: 32
149
149
  npiCode:
150
150
  label: "NPI"
151
- description: "National Provider Identifier"
151
+ description: "National Provider Identifier, issued to health care providers in the United States"
152
152
  type: identifier
153
153
  format: npi
154
154
  maxLength: 16
@@ -2,6 +2,8 @@ from hashlib import sha1
2
2
  from collections.abc import Mapping
3
3
  from typing import Any, Dict, List, Optional, Set, Type
4
4
  from typing import Generator, Iterable, Tuple, TypeVar
5
+ from rigour.langs import LangStr
6
+ from rigour.names.pick import pick_lang_name
5
7
 
6
8
  from followthemoney.model import Model
7
9
  from followthemoney.exc import InvalidData
@@ -163,6 +165,7 @@ class StatementEntity(EntityProxy):
163
165
  else:
164
166
  self.last_change = max(self.last_change, stmt.first_seen)
165
167
  else:
168
+ self._caption = None
166
169
  if stmt.prop not in self._statements:
167
170
  self._statements[stmt.prop] = set()
168
171
  self._statements[stmt.prop].add(stmt)
@@ -296,13 +299,17 @@ class StatementEntity(EntityProxy):
296
299
  prop_name = self._prop_name(prop, quiet=quiet)
297
300
  if prop_name is None or prop_name not in self._statements:
298
301
  return []
302
+ if prop_name in self.schema.caption:
303
+ self._caption = None
299
304
  return list({s.value for s in self._statements.pop(prop_name, [])})
300
305
 
301
306
  def remove(self, prop: P, value: str, quiet: bool = True) -> None:
302
307
  prop_name = self._prop_name(prop, quiet=quiet)
303
- if prop_name is not None and prop_name in self._properties:
308
+ if prop_name is not None and prop_name in self._statements:
304
309
  stmts = {s for s in self._statements[prop_name] if s.value != value}
305
310
  self._statements[prop_name] = stmts
311
+ if prop_name in self.schema.caption:
312
+ self._caption = None
306
313
 
307
314
  def itervalues(self) -> Generator[Tuple[Property, str], None, None]:
308
315
  for name, statements in self._statements.items():
@@ -335,6 +342,34 @@ class StatementEntity(EntityProxy):
335
342
  def properties(self) -> Dict[str, List[str]]:
336
343
  return {p: list({s.value for s in vs}) for p, vs in self._statements.items()}
337
344
 
345
+ @property
346
+ def caption(self) -> str:
347
+ """The user-facing label to be used for this entity. This checks a list
348
+ of properties defined by the schema (caption) and returns the first
349
+ available value. If no caption is available, return the schema label.
350
+
351
+ This implementation prefers statements where the language property is that
352
+ of the preferred system language."""
353
+ if self._caption is None:
354
+ for prop_ in self.schema.caption:
355
+ stmts = self._statements.get(prop_)
356
+ if stmts is None:
357
+ continue
358
+ prop = self.schema.properties[prop_]
359
+ if prop.type == registry.name and len(stmts) > 1:
360
+ values = [LangStr(s.value, lang=s.lang) for s in stmts]
361
+ name = pick_lang_name(values)
362
+ if name is not None:
363
+ self._caption = name
364
+ return self._caption
365
+
366
+ for stmt in sorted(stmts):
367
+ self._caption = stmt.value
368
+ return self._caption
369
+ if self._caption is None:
370
+ self._caption = self.schema.label
371
+ return self._caption
372
+
338
373
  def iterprops(self) -> List[Property]:
339
374
  return [self.schema.properties[p] for p in self._statements.keys()]
340
375
 
@@ -353,8 +388,8 @@ class StatementEntity(EntityProxy):
353
388
  raise InvalidData(msg % (self.id, e))
354
389
 
355
390
  if not isinstance(other, StatementEntity):
356
- for prop, values in other._properties.items():
357
- self.add(prop, values, cleaned=True, quiet=True)
391
+ for prop, value in other.itervalues():
392
+ self.unsafe_add(prop, value, cleaned=True, quiet=True)
358
393
  return self
359
394
  for stmt in other._iter_stmt():
360
395
  if self.id is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: followthemoney
3
- Version: 4.2.0
3
+ Version: 4.2.2
4
4
  Summary: A data model for anti corruption data modeling and analysis.
5
5
  Project-URL: Documentation, https://followthemoney.tech/
6
6
  Project-URL: Repository, https://github.com/opensanctions/followthemoney.git
@@ -42,15 +42,15 @@ Requires-Dist: click<9.0.0,>=8.0
42
42
  Requires-Dist: networkx<3.5,>=2.5
43
43
  Requires-Dist: normality<4.0.0,>=3.0.1
44
44
  Requires-Dist: openpyxl<4.0.0,>=3.0.5
45
- Requires-Dist: orjson<4.0,>=3.10.18
45
+ Requires-Dist: orjson<4.0,>=3.10.0
46
46
  Requires-Dist: phonenumbers<10.0.0,>=8.12.22
47
- Requires-Dist: prefixdate<1.0.0,>=0.4.0
48
- Requires-Dist: pydantic<3.0.0,>=2.11.7
47
+ Requires-Dist: prefixdate<1.0.0,>=0.5.0
48
+ Requires-Dist: pydantic<3.0.0,>=2.11.0
49
49
  Requires-Dist: pytz>=2021.1
50
50
  Requires-Dist: pyyaml<7.0.0,>=5.0.0
51
51
  Requires-Dist: rdflib<7.2.0,>=6.2.0
52
52
  Requires-Dist: requests<3.0.0,>=2.21.0
53
- Requires-Dist: rigour<2.0.0,>=1.2.0
53
+ Requires-Dist: rigour<2.0.0,>=1.3.1
54
54
  Requires-Dist: sqlalchemy[mypy]<3.0.0,>=2.0.0
55
55
  Provides-Extra: dev
56
56
  Requires-Dist: build; extra == 'dev'
@@ -1,4 +1,4 @@
1
- followthemoney/__init__.py,sha256=NqtJnba71wycGIhiZrBIfOYbafL_Tm_Qd0ouNm2EYPA,856
1
+ followthemoney/__init__.py,sha256=pTkibO7jJUHnObuIGY1rsjFL9siMpBusU7E9OylQDBI,856
2
2
  followthemoney/compare.py,sha256=bZlnj2VMoe67q4Lyq_VwS1a-EJnEK1kC8prbs8jyL9E,5774
3
3
  followthemoney/entity.py,sha256=bBiX7hNquXemS3vYCUHKtWI_IqX43Z6i8RQDbZ7gXsg,3449
4
4
  followthemoney/exc.py,sha256=GyMgwY4QVm87hLevDfV7gM1MJsDqfNCi_UQw7F_A8X8,858
@@ -9,7 +9,7 @@ followthemoney/model.py,sha256=bWFVNa-DhYzc8BdSXBZdG2ev6Nh9uHx6i4tin8DvEEU,7374
9
9
  followthemoney/names.py,sha256=LODQqExKEHdH4z6Mmbhlm0KeKRzGcptaSWzYXZ7lONI,1120
10
10
  followthemoney/namespace.py,sha256=utggu9IGA8bhgEYom3OUB1KxkAJR_TrMNbY5MUF_db8,4536
11
11
  followthemoney/ontology.py,sha256=WWY_PYQGl5Ket4zZBuZglzQxD2Bh9UqHok6GJNNX7GA,3001
12
- followthemoney/property.py,sha256=RDTzTXJeeLFLptQL1_gr1S1T-vdDe-8MGMwsRaGQh0I,7665
12
+ followthemoney/property.py,sha256=9qZ_o2iA-1llLMJ3O2hsW7c2XhkFU1YbvVqretGYUSA,7913
13
13
  followthemoney/proxy.py,sha256=LD4K1oPABXMX212UZxwLu7XOHRDyVBwTlqudTUsUZRQ,19619
14
14
  followthemoney/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  followthemoney/schema.py,sha256=WYnPE4Lego0pJHlojECEv0aO9Miw_YIvEb35HoDo4Zk,18087
@@ -54,7 +54,7 @@ followthemoney/schema/Audio.yaml,sha256=Eb1rZGUEOX7XDAj_1YIN28NCBzMvkopQBNwgHt_k
54
54
  followthemoney/schema/BankAccount.yaml,sha256=60v-VD296lW1Qq7fx--CzxfPNwfCcyMV6xIl8OrSy5g,1431
55
55
  followthemoney/schema/Call.yaml,sha256=kbVCnVxucBrEplxehXHThLSJAJjy_GhWan-IeZZjr0M,980
56
56
  followthemoney/schema/CallForTenders.yaml,sha256=2IWonTzfSbrkynMoEWqv5fekUeFM_xDKpKIbRe1XDbo,3227
57
- followthemoney/schema/Company.yaml,sha256=v16tU4mQhVitC3BOc9OPbubCxPgf1c-Iudnyq6IF0ys,3057
57
+ followthemoney/schema/Company.yaml,sha256=v6OFHZjEIMQPSP9re6nAhI2kLk1MHfP9LfcCc7M-Ifc,3432
58
58
  followthemoney/schema/Contract.yaml,sha256=aSPB64T1h-0nuLDv6krasUvvoPZgo6sWUbv60c3vmzI,1541
59
59
  followthemoney/schema/ContractAward.yaml,sha256=b2spaZHYCaP1yR1RCsrI7mUjk-fAF7BUE3dc8Vl3cUQ,1689
60
60
  followthemoney/schema/CourtCase.yaml,sha256=lcovnY0Ne_xcggvkqfCW_RHvsRKo8kFTCPCyovAXRtI,599
@@ -75,7 +75,7 @@ followthemoney/schema/Identification.yaml,sha256=6txjZs6-3Kn94c3G4tDeDt9Jb4FW55-
75
75
  followthemoney/schema/Image.yaml,sha256=wuznboWECGiV96_GQiXq1-oKNoxO8zKisR4xyusnEn8,394
76
76
  followthemoney/schema/Interest.yaml,sha256=VUrehmsN1WgtS1oAa5jn_JGtSkZGGYLGNahp-R5JhOQ,282
77
77
  followthemoney/schema/Interval.yaml,sha256=8YJQ51GI-GxvbjYs3uC593kQtCepWW_7ZiNnlbPm2aM,2084
78
- followthemoney/schema/LegalEntity.yaml,sha256=u6GOemHj1vnL_6dcC3XyLaP72JQrNecXlbocXxJo5AI,4502
78
+ followthemoney/schema/LegalEntity.yaml,sha256=S56ALyeCZVgjpNy9EqkVkPBIs6QaGuZw31s9bdtEeC4,4789
79
79
  followthemoney/schema/License.yaml,sha256=bXESXY-JpSmc5sthZe4sssXhx50UoLPAMED9FvEUyRU,534
80
80
  followthemoney/schema/Membership.yaml,sha256=IPmaOX4Ai2r4sGcA5ig2WmLvWHb38akdxp4smEdDWOE,710
81
81
  followthemoney/schema/Mention.yaml,sha256=nBeulR_Jm4x75aJ7yNF0TAVhHJqXQaEzOutLIn_YU-4,1086
@@ -114,7 +114,7 @@ followthemoney/schema/Vessel.yaml,sha256=zWHUfSK8g6Pz58ZyCaK0AFJ4u_UHjEIUGC4c_7o
114
114
  followthemoney/schema/Video.yaml,sha256=LY3DYMWTHXiAhL0hxBCNCz50cp2sPbUlEhhig5Fbjos,327
115
115
  followthemoney/schema/Workbook.yaml,sha256=iikWPElz4klA7SkWH7eae6xqhbkMCIP_3zdeXzFEMU0,354
116
116
  followthemoney/statement/__init__.py,sha256=7m2VUCAuqNZXIY0WFJRFkw5UG14QuxATL4f_xbqKwhw,633
117
- followthemoney/statement/entity.py,sha256=MKHGmFeDwcW2lTbAeKSdU53YwDuoLm5iKy4roeb8_lo,16172
117
+ followthemoney/statement/entity.py,sha256=mUf-6uvrYvqq9azdHu7qFGkGK_0j9gT6nxscX_JXWR0,17673
118
118
  followthemoney/statement/serialize.py,sha256=9eXzQ1biR2mSxWRID5C7xDdku4b4ZImHeRJ53yLZ0yo,7225
119
119
  followthemoney/statement/statement.py,sha256=Ae-EYuzS8S12BkaRqrvMuI1C7YwlRKa5C_pTBELyNMM,8029
120
120
  followthemoney/statement/util.py,sha256=B-ozuRc1TWvpop52873Pqt5OPj8H6uk4KyRJLfAhr10,780
@@ -161,8 +161,8 @@ followthemoney/types/phone.py,sha256=r8uRqWinS0CYnYBTs405k5gO4jeatUDgjdzzijoMKJE
161
161
  followthemoney/types/string.py,sha256=fqyTauAm4mNnNaoH-yH087RBbNh-G5ZZUO3awTGQUUg,1230
162
162
  followthemoney/types/topic.py,sha256=Mi0Gx0m3bDeTmyuvM6jdRMqv81O03U4eI99R13KGu2Y,4503
163
163
  followthemoney/types/url.py,sha256=QFpS_JIV8unFHuh_uGv22SWUUkocBoOpzLsAJWom_gI,1455
164
- followthemoney-4.2.0.dist-info/METADATA,sha256=I1JXOtpniNIMi2jPoM1Vpx_IaYezqCP8l9e3qhldGtc,6748
165
- followthemoney-4.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
166
- followthemoney-4.2.0.dist-info/entry_points.txt,sha256=caoFTlf213jhg5sz3TNSofutjUTzaKtWATuSIdd9Cps,653
167
- followthemoney-4.2.0.dist-info/licenses/LICENSE,sha256=H6_EVXisnJC0-18CjXIaqrBSFq_VH3OnS7u3dccOv6g,1148
168
- followthemoney-4.2.0.dist-info/RECORD,,
164
+ followthemoney-4.2.2.dist-info/METADATA,sha256=Q7n2yZuDQJM27GTkVB0nMJgbMSVJLJ1RNCMmzABRFbY,6747
165
+ followthemoney-4.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
166
+ followthemoney-4.2.2.dist-info/entry_points.txt,sha256=caoFTlf213jhg5sz3TNSofutjUTzaKtWATuSIdd9Cps,653
167
+ followthemoney-4.2.2.dist-info/licenses/LICENSE,sha256=H6_EVXisnJC0-18CjXIaqrBSFq_VH3OnS7u3dccOv6g,1148
168
+ followthemoney-4.2.2.dist-info/RECORD,,