followthemoney 3.8.4__py3-none-any.whl → 3.8.5__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.

@@ -3,7 +3,7 @@ import os
3
3
  from followthemoney.model import Model
4
4
  from followthemoney.util import set_model_locale
5
5
 
6
- __version__ = "3.8.4"
6
+ __version__ = "3.8.5"
7
7
 
8
8
 
9
9
  model_path = os.path.dirname(__file__)
@@ -1,17 +1,8 @@
1
- import sys
2
-
3
-
4
1
  def load_entry_points() -> None:
5
- if sys.version_info[0] >= 3 and sys.version_info[1] >= 10:
6
- from importlib.metadata import entry_points
7
-
8
- for ep in entry_points().select(group="followthemoney.cli"):
9
- ep.load()
10
- else:
11
- from pkg_resources import iter_entry_points
2
+ from importlib.metadata import entry_points
12
3
 
13
- for ep_ in iter_entry_points("followthemoney.cli"):
14
- ep_.load()
4
+ for ep in entry_points().select(group="followthemoney.cli"):
5
+ ep.load()
15
6
 
16
7
 
17
8
  load_entry_points()
@@ -40,7 +40,7 @@ def aggregate(infile: Path, outfile: Path) -> None:
40
40
  entity = namespace.apply(entity)
41
41
  if entity.id in buffer:
42
42
  buffer[entity.id].merge(entity)
43
- else:
43
+ elif entity.id is not None:
44
44
  buffer[entity.id] = entity
45
45
 
46
46
  for entity in buffer.values():
@@ -60,22 +60,24 @@ def run_mapping(outfile: Path, mapping_yaml: Path, sign: bool = True) -> None:
60
60
  def stream_mapping(
61
61
  infile: Path, outfile: Path, mapping_yaml: Path, sign: bool = True
62
62
  ) -> None:
63
- queries: List[Tuple[str, QueryMapping]] = []
63
+ queries: List[Tuple[str, QueryMapping, CSVSource]] = []
64
64
  config = load_mapping_file(mapping_yaml)
65
65
  for dataset, meta in config.items():
66
66
  for data in keys_values(meta, "queries", "query"):
67
67
  data.pop("database", None)
68
68
  data["csv_url"] = "/dev/null"
69
69
  query = model.make_mapping(data, key_prefix=dataset)
70
- queries.append((dataset, query))
70
+ source = query.source
71
+ assert isinstance(source, CSVSource)
72
+ queries.append((dataset, query, source))
71
73
 
72
74
  try:
73
75
  with path_writer(outfile) as outfh:
74
76
  with input_file(infile) as fh:
75
77
  for record in CSVSource.read_csv(fh):
76
- for (dataset, query) in queries:
78
+ for dataset, query, source in queries:
77
79
  ns = Namespace(dataset)
78
- if query.source.check_filters(record): # type: ignore
80
+ if source.check_filters(record):
79
81
  entities = query.map(record)
80
82
  for entity in entities.values():
81
83
  if sign:
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from typing import Iterable, Optional
4
4
 
5
5
  from followthemoney import model
6
- from followthemoney.proxy import E, EntityProxy
6
+ from followthemoney.proxy import EntityProxy
7
7
  from followthemoney.types import registry
8
8
  from followthemoney.cli.cli import cli
9
9
  from followthemoney.cli.util import InPath, OutPath, path_entities
@@ -1,6 +1,6 @@
1
1
  from typing import Generator, List, Optional, Tuple
2
2
  from followthemoney.property import Property
3
- from followthemoney.proxy import E
3
+ from followthemoney.proxy import EntityProxy
4
4
  from followthemoney.schema import Schema
5
5
  from followthemoney.types import registry
6
6
 
@@ -17,12 +17,12 @@ class Exporter(object):
17
17
  yield prop
18
18
 
19
19
  def exportable_fields(
20
- self, proxy: E
20
+ self, proxy: EntityProxy
21
21
  ) -> Generator[Tuple[Property, List[str]], None, None]:
22
22
  for prop in self.exportable_properties(proxy.schema):
23
23
  yield prop, proxy.get(prop)
24
24
 
25
- def write(self, proxy: E, extra: Optional[List[str]] = None) -> None:
25
+ def write(self, proxy: EntityProxy, extra: Optional[List[str]] = None) -> None:
26
26
  raise NotImplementedError
27
27
 
28
28
  def finalize(self) -> None:
@@ -1,21 +1,19 @@
1
1
  import csv
2
-
3
- try:
4
- from _csv import _writer as csv_writer
5
- except ImportError:
6
- # Python 3.8/3.9 work-around:
7
- from _csv import writer as csv_writer # type: ignore
8
-
9
- from io import TextIOWrapper
10
2
  from pathlib import Path
11
- from typing import Dict, List, Optional, Tuple
3
+ from io import TextIOWrapper
4
+ from typing import Any, Dict, List, Optional, Protocol, Tuple
12
5
 
13
- from followthemoney.proxy import E
6
+ from followthemoney.proxy import EntityProxy
14
7
  from followthemoney.export.common import Exporter
15
8
  from followthemoney.schema import Schema
16
9
  from followthemoney.util import PathLike
17
10
 
18
- CSVWriter = csv_writer
11
+
12
+ class CSVWriter(Protocol):
13
+ @property
14
+ def dialect(self) -> Any: ...
15
+ def writerow(self, row: Any) -> Any: ...
16
+ def writerows(self, rows: Any) -> None: ...
19
17
 
20
18
 
21
19
  class CSVMixin(object):
@@ -69,7 +67,7 @@ class CSVExporter(Exporter, CSVMixin):
69
67
  headers.append(prop.name)
70
68
  writer.writerow(headers)
71
69
 
72
- def write(self, proxy: E, extra: Optional[List[str]] = None) -> None:
70
+ def write(self, proxy: EntityProxy, extra: Optional[List[str]] = None) -> None:
73
71
  writer = self._get_writer(proxy.schema)
74
72
  cells = [proxy.id]
75
73
  cells.extend(extra or [])
@@ -150,7 +150,7 @@ class CypherGraphExporter(GraphExporter):
150
150
  labels = list(node.schema.names)
151
151
  else:
152
152
  labels = [node.type.name]
153
- cypher = "MERGE (p { %(id)s }) " "SET p += { %(map)s } SET p :%(label)s;\n"
153
+ cypher = "MERGE (p { %(id)s }) SET p += { %(map)s } SET p :%(label)s;\n"
154
154
  self.fh.write(
155
155
  cypher
156
156
  % {
followthemoney/graph.py CHANGED
@@ -5,6 +5,7 @@ This module provides an abstract data object that represents a property
5
5
  graph. This is used by the exporter modules to convert data
6
6
  to a specific output format, like Cypher or NetworkX.
7
7
  """
8
+
8
9
  import logging
9
10
  from typing import Any, Dict, Generator, Iterable, List, Optional
10
11
 
@@ -69,6 +70,8 @@ class Node(object):
69
70
  def from_proxy(cls, proxy: EntityProxy) -> "Node":
70
71
  """For a given :class:`~followthemoney.proxy.EntityProxy`, return a node
71
72
  based on the entity."""
73
+ if proxy.id is None:
74
+ raise InvalidModel("Invalid entity proxy: %r" % proxy)
72
75
  return cls(registry.entity, proxy.id, proxy=proxy)
73
76
 
74
77
  def __str__(self) -> str:
@@ -256,11 +259,11 @@ class Graph(object):
256
259
  """Add an :class:`~followthemoney.proxy.EntityProxy` to the graph and make
257
260
  it either a :class:`~followthemoney.graph.Node` or an
258
261
  :class:`~followthemoney.graph.Edge`."""
259
- if proxy is None:
262
+ if proxy is None or proxy.id is None:
260
263
  return
261
264
  self.queue(proxy.id, proxy)
262
265
  if proxy.schema.edge:
263
- for (source, target) in proxy.edgepairs():
266
+ for source, target in proxy.edgepairs():
264
267
  self._add_edge(proxy, source, target)
265
268
  else:
266
269
  self._add_node(proxy)
@@ -1,24 +1,12 @@
1
1
  import io
2
2
  import os
3
3
  import logging
4
- from banal.lists import ensure_list
5
4
  import requests
6
5
  from csv import DictReader
7
6
  from urllib.parse import urlparse
8
- from banal import keys_values
9
- from typing import (
10
- TYPE_CHECKING,
11
- Any,
12
- Dict,
13
- Generator,
14
- ItemsView,
15
- Iterable,
16
- List,
17
- Optional,
18
- Set,
19
- Tuple,
20
- cast,
21
- )
7
+ from banal import keys_values, ensure_list
8
+ from typing import TYPE_CHECKING, cast
9
+ from typing import Any, Dict, Generator, ItemsView, Iterable, List, Optional, Set, Tuple
22
10
 
23
11
  from followthemoney.mapping.source import Record, Source
24
12
  from followthemoney.util import sanitize_text
@@ -48,16 +36,16 @@ class CSVSource(Source):
48
36
 
49
37
  def _parse_filters(self, filters: ItemsView[str, Any]) -> FilterList:
50
38
  filters_set: FilterList = []
51
- for (key, value) in filters:
39
+ for key, value in filters:
52
40
  values = set(cast(List[Optional[str]], ensure_list(value)))
53
41
  filters_set.append((key, values))
54
42
  return filters_set
55
43
 
56
44
  def check_filters(self, data: Record) -> bool:
57
- for (k, v) in self.filters_set:
45
+ for k, v in self.filters_set:
58
46
  if data.get(k) not in v:
59
47
  return False
60
- for (k, v) in self.filters_not_set:
48
+ for k, v in self.filters_not_set:
61
49
  if data.get(k) in v:
62
50
  return False
63
51
  return True
@@ -3,8 +3,7 @@ import logging
3
3
  from uuid import uuid4
4
4
  from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Union, cast
5
5
  from banal import ensure_list, is_listish, keys_values
6
- from sqlalchemy import MetaData, func
7
- from sqlalchemy.future import select
6
+ from sqlalchemy import MetaData, func, select
8
7
  from sqlalchemy.engine import Engine, create_engine
9
8
  from sqlalchemy.sql.elements import Label
10
9
  from sqlalchemy.pool import NullPool
@@ -68,7 +67,7 @@ class SQLSource(Source):
68
67
  return table.refs[ref]
69
68
  raise InvalidMapping("Missing reference: %s" % ref)
70
69
 
71
- def apply_filters(self, q: Select) -> Select:
70
+ def apply_filters(self, q: Select[Any]) -> Select[Any]:
72
71
  for col, val in self.filters:
73
72
  if is_listish(val):
74
73
  q = q.where(self.get_column(col).in_(val))
@@ -88,7 +87,7 @@ class SQLSource(Source):
88
87
  q = q.where(left == right)
89
88
  return q
90
89
 
91
- def compose_query(self) -> Select:
90
+ def compose_query(self) -> Select[Any]:
92
91
  columns = [self.get_column(r) for r in self.query.refs]
93
92
  q = select(*columns)
94
93
  q = q.select_from(*[t.alias for t in self.tables])
@@ -22,6 +22,7 @@ that the combined ID is specific to a dataset, without needing an (expensive)
22
22
  index look up of each ID first. It can also be generated on the client or
23
23
  the server without compromising isolation.
24
24
  """
25
+
25
26
  import hmac
26
27
  from typing import Any, Optional, Tuple, Union
27
28
 
@@ -95,7 +96,8 @@ class Namespace(object):
95
96
  """Rewrite an entity proxy so all IDs mentioned are limited to
96
97
  the namespace."""
97
98
  signed = proxy.clone()
98
- signed.id = self.sign(proxy.id)
99
+ if proxy.id is not None:
100
+ signed.id = self.sign(proxy.id)
99
101
  if not shallow:
100
102
  for prop in proxy.iterprops():
101
103
  if prop.type != registry.entity:
followthemoney/proxy.py CHANGED
@@ -71,7 +71,7 @@ class EntityProxy(object):
71
71
  #: A unique identifier for this entity, usually a hashed natural key,
72
72
  #: a UUID, or a very simple slug. Can be signed using a
73
73
  #: :class:`~followthemoney.namespace.Namespace`.
74
- self.id = data.pop("id", None)
74
+ self.id = str(data["id"]) if "id" in data else None
75
75
  if not cleaned:
76
76
  self.id = sanitize_text(self.id)
77
77
 
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: followthemoney
3
- Version: 3.8.4
3
+ Version: 3.8.5
4
4
  Summary: A data model for anti corruption data modeling and analysis.
5
5
  Project-URL: Documentation, https://followthemoney.tech/
6
- Project-URL: Repository, https://github.com/alephdata/followthemoney.git
7
- Project-URL: Issues, https://github.com/alephdata/followthemoney/issues
8
- Author-email: Organized Crime and Corruption Reporting Project <data@occrp.org>, OpenSanctions <info@opensanctions.org>
6
+ Project-URL: Repository, https://github.com/opensanctions/followthemoney.git
7
+ Project-URL: Issues, https://github.com/opensanctions/followthemoney/issues
8
+ Author-email: OpenSanctions <info@opensanctions.org>, DARC <hi@dataresearchcenter.org>
9
9
  License: MIT License
10
10
 
11
11
  Copyright (c) 2017-2024 Journalism Development Network, Inc.
12
+ Copyright (c) 2025 OpenSanctions Datenbanken GmbH
12
13
 
13
14
  Permission is hereby granted, free of charge, to any person obtaining a copy
14
15
  of this software and associated documentation files (the "Software"), to deal
@@ -51,8 +52,7 @@ Requires-Dist: pyyaml<7.0.0,>=5.0.0
51
52
  Requires-Dist: rdflib<7.2.0,>=6.2.0
52
53
  Requires-Dist: requests<3.0.0,>=2.21.0
53
54
  Requires-Dist: rigour<1.0.0,>=0.11.1
54
- Requires-Dist: sqlalchemy2-stubs
55
- Requires-Dist: sqlalchemy<3.0.0,>=1.4.49
55
+ Requires-Dist: sqlalchemy[mypy]<3.0.0,>=2.0.0
56
56
  Requires-Dist: types-pyyaml
57
57
  Provides-Extra: dev
58
58
  Requires-Dist: build; extra == 'dev'
@@ -76,17 +76,13 @@ Description-Content-Type: text/markdown
76
76
 
77
77
  # Follow the Money
78
78
 
79
- [![ftm-build](https://github.com/alephdata/followthemoney/actions/workflows/build.yml/badge.svg)](https://github.com/alephdata/followthemoney/actions/workflows/build.yml)
79
+ [![ftm-build](https://github.com/opensanctions/followthemoney/actions/workflows/build.yml/badge.svg)](https://github.com/opensanctions/followthemoney/actions/workflows/build.yml)
80
80
 
81
- This repository contains a pragmatic data model for the entities most
82
- commonly used in investigative reporting: people, companies, assets,
83
- payments, court cases, etc.
81
+ This repository contains a pragmatic data model for the entities most commonly used in investigative reporting and financial crime investigations: people, companies, assets, payments, ownership relations, court cases, etc.
84
82
 
85
- The purpose of this is not to model reality in an ideal data model, but
86
- rather to have a working data structure for researchers.
83
+ The purpose of this is not to model reality in an ideal data model, but rather to have a working data structure for researchers. Complex legal considerations are simplified to allow for efficient data processing.
87
84
 
88
- `followthemoney` also contains code used to validate and normalize many
89
- of the elements of data, and to map tabular data into the model.
85
+ `followthemoney` also contains code used to validate and normalize many of the elements of data, and to map tabular data into the model.
90
86
 
91
87
  ## Documentation
92
88
 
@@ -104,12 +100,11 @@ library and the contained ontology:
104
100
 
105
101
  * https://followthemoney.tech/explorer/
106
102
 
107
- There's also a number of viewers for the RDF schema definitions generated
108
- from FollowTheMoney, e.g.:
103
+ There's also a number of viewers for the RDF schema definitions generated from FollowTheMoney, eg:
109
104
 
110
- * [LODE documentation](http://150.146.207.114/lode/extract?url=https%3A%2F%2Falephdata.github.io%2Ffollowthemoney%2Fns%2Fftm.xml&owlapi=true&imported=true&lang=en)
111
- * [WebVOWL](https://service.tib.eu/webvowl/#iri=https://alephdata.github.io/followthemoney/ns/ftm.xml)
112
- * RDF/OWL specification in [XML](https://alephdata.github.io/followthemoney/ns/ftm.xml).
105
+ * [LODE documentation](http://150.146.207.114/lode/extract?url=https%3A%2F%2Ffollowthemoney.tech%2Fns%2Fftm.xml&owlapi=true&imported=true&lang=en)
106
+ * [WebVOWL](https://service.tib.eu/webvowl/#iri=https://followthemoney.tech/ns/ftm.xml)
107
+ * RDF/OWL specification in [XML](https://followthemoney.tech/ns/ftm.xml).
113
108
 
114
109
  ## Development environment
115
110
 
@@ -129,9 +124,7 @@ make test
129
124
 
130
125
  ## Releasing
131
126
 
132
- We release a lot of version of `followthemoney` because even small changes
133
- to the code base require a pypi release to begin being used in `aleph`. To
134
- this end, here's the steps for making a release:
127
+ We release a lot of version of `followthemoney` because even small changes to the code base require a pypi release to begin being used in `aleph`. To this end, here's the steps for making a release:
135
128
 
136
129
  ```bash
137
130
  git pull --rebase
@@ -142,10 +135,6 @@ bumpversion patch
142
135
  git push --atomic origin main $(git describe --tags --abbrev=0)
143
136
  ```
144
137
 
145
- This will create a new patch release and upload a distribution of it. If
146
- the changes are more significant, you can run `bumpversion` with the `minor`
147
- or `major` arguments.
138
+ This will create a new patch release and upload a distribution of it. If the changes are more significant, you can run `bumpversion` with the `minor` or `major` arguments.
148
139
 
149
- When the schema is updated, please update the docs, ideally including the
150
- diagrams. For the RDF namespace and JavaScript version of the model,
151
- run `make generate`.
140
+ When the schema is updated, please update the docs, ideally including the diagrams. For the RDF namespace and JavaScript version of the model, run `make generate`.
@@ -1,40 +1,40 @@
1
- followthemoney/__init__.py,sha256=7oKn-HF4NWWl-ZAFITBRDLydUFGKVOFnmSZH51hf9ak,360
1
+ followthemoney/__init__.py,sha256=GTdXf-mlMlK2HNFSllcTQHcDOhWxcMhLGJB7OeyP29I,360
2
2
  followthemoney/compare.py,sha256=1GFkCfTzA8QR0CH90kvySR8hvl9hQRUerW5Xw2Ivmpg,5134
3
3
  followthemoney/exc.py,sha256=ynZs_UnTVxHR-iBfat_CpVLraYzVX5yLtVf5Ti14hl4,734
4
- followthemoney/graph.py,sha256=VNDKrUBkz_-DmKsr5v-Xm8VfxzabnTwkU_MFk92_TjA,10848
4
+ followthemoney/graph.py,sha256=9ZNXI3cPR3NFO0hzDj19c1Nx9bgkRWD_EIRgG6zWTPU,10963
5
5
  followthemoney/helpers.py,sha256=Btb6BlHg_c-qCXZo-NP_LURKG-qu-QD3Fj1ev_c7Xic,7956
6
6
  followthemoney/messages.py,sha256=zUEa9CFecU8nRafIzhN6TKCh1kEihiIyIS1qr8PxY4g,806
7
7
  followthemoney/model.py,sha256=FBY6iSbfvsxdjwFlwhs234IbYtl_MwEeTTmoxIFBxC0,6485
8
- followthemoney/namespace.py,sha256=qYplxKn5OO3zDMf3NItKwTwDsJOnski5JbeyhssqhR8,4434
8
+ followthemoney/namespace.py,sha256=cp7X8aGaZ8HHf7SOfHr2vJHPI2todz2DoyLdiZLNMyg,4472
9
9
  followthemoney/offshore.py,sha256=Pf0tx-7GyhIZRueshDRqPNlxkHfGstmW5yNDID5Mnws,1060
10
10
  followthemoney/ontology.py,sha256=7PEoUKISNpkRvVhuLeE3IE9ZiNtdR8mn9_kzZ9yF9l0,2986
11
11
  followthemoney/property.py,sha256=zi9ss1v0e8Wmv-FuLtZd2aod5iTLfBekBxuOTgIOUMU,7718
12
- followthemoney/proxy.py,sha256=M7RIPF0k-P3v7GYKYhRVVaO1cnUf5FArJepE-2c0KMQ,20033
12
+ followthemoney/proxy.py,sha256=PIuo38ev_MJxamO_0MGKh2MdgJiwaletZbq3uKO3Yv4,20054
13
13
  followthemoney/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  followthemoney/rdf.py,sha256=9wPs-tqN9QILuvrmH_YheNaFQctyIQqoIEqcS465QHs,343
15
15
  followthemoney/schema.py,sha256=Yqv11zhZXMOq1MjKQP44Ie8vIurDrSr5qlqRJEueLK4,18126
16
16
  followthemoney/util.py,sha256=6VR-T5-vT-8xpE6__Skrqe_MpE6y6csvNKERTJlsITA,4527
17
- followthemoney/cli/__init__.py,sha256=Fl05wMr5-FlOSMRpmu1HJSNfPRpy8u9as5IRbGXdo4U,421
18
- followthemoney/cli/aggregate.py,sha256=Gwfi5Bt1LCwqbpsCu4P1Cr-QJtCWhbaqgGEzfwJUUL4,2142
17
+ followthemoney/cli/__init__.py,sha256=0mmz84uhXRp2qUn3syKnDXofU3MMAAe291s7htqX0Bg,187
18
+ followthemoney/cli/aggregate.py,sha256=xQTFpU3cVVj7fplpX4OJVrRlTVpn6b9kBr_Vb87pKfg,2164
19
19
  followthemoney/cli/cli.py,sha256=yrPw2iyKY-E-uRWe6KN9W3ayvz-22vfpe_ZeD0RiI0c,3591
20
20
  followthemoney/cli/exports.py,sha256=HsTyIOz1KQSeObp9-9SKzSUBW158XOhpU5_Stv_2HWM,4016
21
- followthemoney/cli/mapping.py,sha256=aEl57en0zu57yMA2AU5y01U5Yyqo_42hkMlAdfIQP08,3284
22
- followthemoney/cli/sieve.py,sha256=Wh1UQxzyM9Gh60ooS4s4ydlW1b69bMzFM08tg8ttSIY,1940
21
+ followthemoney/cli/mapping.py,sha256=PGQ-9T5ss6w6qnZg7IjUZZ3PplY15CcPSxZxkyMFLDM,3370
22
+ followthemoney/cli/sieve.py,sha256=wLB35fCVp1ArZ7FDTbARevBk8jH4vnp65fyBZU7Lk_k,1937
23
23
  followthemoney/cli/util.py,sha256=CFcS-PEwpMasMWX_Yg283O_PaAhcPwkvahFNWc13C8c,4769
24
24
  followthemoney/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- followthemoney/export/common.py,sha256=_YrXrwsqmyboDZDhtJ_PazUUJYe1Y-Trqc9lz4YlVR8,991
26
- followthemoney/export/csv.py,sha256=AvWgyWg0nICAVLv4k9QgWRlgJhJonKCjzlZkrahe5Tg,2633
25
+ followthemoney/export/common.py,sha256=5b-Qlu3MaA0kSzzMAP93FAWncpgiioENnCnHikWYxhs,1021
26
+ followthemoney/export/csv.py,sha256=reWq1jYIv7sY2PEI4JwIxahYNNqnSiPfMCS3kQX4RZ8,2652
27
27
  followthemoney/export/excel.py,sha256=pj6zNpIbye_Zm3vhCamcqHEe9Fw-RyjtWQDCFY6608s,2645
28
28
  followthemoney/export/graph.py,sha256=v0z1FgadyFk5aQ0A5q8E9R4fSO-Tpi5JU9YTDwnRKD8,2765
29
- followthemoney/export/neo4j.py,sha256=aw1vBOLTRinFbOhn19WThLlQ6NWVbm2gop5Jz_ihc58,7052
29
+ followthemoney/export/neo4j.py,sha256=4Lih9lt3-5ATERhyMcfJfkiETG3tqj9vY4N9s7jiYmw,7049
30
30
  followthemoney/export/rdf.py,sha256=E6RiW7oIsJdaBaLAVm6o-MTokARZtqONPuayILqTqo0,786
31
31
  followthemoney/mapping/__init__.py,sha256=iwNqzzvrzJNbNDlOCaDLlBTUrNTlnYHIB5cvo_-9oN4,82
32
- followthemoney/mapping/csv.py,sha256=Tvc6VSh7-ca63LEE4G0yqOCeGMETkuKzUjIkVn4_d7Q,3185
32
+ followthemoney/mapping/csv.py,sha256=1eqQk1tn5JSEcr4rrv44XdT5biUk7J0E275uvUNoOrA,3125
33
33
  followthemoney/mapping/entity.py,sha256=-x_VBHiVthIrZZ-PVKD3oBAq6LYcsyeYW-9TFv80k7M,5905
34
34
  followthemoney/mapping/property.py,sha256=41V16HJh6da7oKdSJWyRcyMkx2XFd6iDm9-4PH7Wihw,5036
35
35
  followthemoney/mapping/query.py,sha256=8M6bOlEX2p_bbVwEwTu_1slEtU0cfRJB7ajZp-F07CE,2622
36
36
  followthemoney/mapping/source.py,sha256=sri-XpSjeHZbQtqNcz1eJYvwVSBNqGO5JwhWswiEx3c,649
37
- followthemoney/mapping/sql.py,sha256=m3Ho8B2pFsg6q2zj-y55h0O3x9eb6wzjlPBQyZ6DVcI,4752
37
+ followthemoney/mapping/sql.py,sha256=iCSOl9uOzJI__1d1XvLtJmLUXhb38MRM_eBnYt7TdP8,4738
38
38
  followthemoney/schema/Address.yaml,sha256=9cPZfkNiV-5fNfSzBFcE_dwD0V2_WLpGSLT6kL8GbuQ,1709
39
39
  followthemoney/schema/Airplane.yaml,sha256=i3tI4INH6ZtKBqm09fAUtCbieFlha-vPfQURXMDZTIU,622
40
40
  followthemoney/schema/Analyzable.yaml,sha256=WvyhjYqI7QzBt3qPDgO7MDasJH1qQtgQMToHkV_xtlU,1237
@@ -150,8 +150,8 @@ followthemoney/types/registry.py,sha256=hvQ1oIWz_4PpyUnKA5azbaZCulNb5LCIPyC-AQYV
150
150
  followthemoney/types/string.py,sha256=grDn1OgKWxIzVxGEdp0HjVKIqtQ9W4SW2ty4quv3Pcs,1202
151
151
  followthemoney/types/topic.py,sha256=FqV_4YDpmQxZO9m05l3c-nDyDqsZONqBG5aaf5aCkvA,3797
152
152
  followthemoney/types/url.py,sha256=r7Pd6Yfn--amwMi_nHoTLMwm5SH8h50SMgQaa2G4PJ0,1492
153
- followthemoney-3.8.4.dist-info/METADATA,sha256=hO1oyvwXzk7pDe_Ex8Y-_fydsD8xVb18Iw9Gue6PgM0,6065
154
- followthemoney-3.8.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
155
- followthemoney-3.8.4.dist-info/entry_points.txt,sha256=xvTXjAz0CiZplq4V3iQXlmBexaJyW3zNucIvcDP6L_c,593
156
- followthemoney-3.8.4.dist-info/licenses/LICENSE,sha256=3tfmmk9RtT1eh67a-NDRwcmOLzztbCtqlHW6O1U92ZA,1098
157
- followthemoney-3.8.4.dist-info/RECORD,,
153
+ followthemoney-3.8.5.dist-info/METADATA,sha256=cOEWYZHcj__KvWhrH43Vtj0DZc998eeRk533xAa0lXw,6168
154
+ followthemoney-3.8.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
155
+ followthemoney-3.8.5.dist-info/entry_points.txt,sha256=xvTXjAz0CiZplq4V3iQXlmBexaJyW3zNucIvcDP6L_c,593
156
+ followthemoney-3.8.5.dist-info/licenses/LICENSE,sha256=H6_EVXisnJC0-18CjXIaqrBSFq_VH3OnS7u3dccOv6g,1148
157
+ followthemoney-3.8.5.dist-info/RECORD,,
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2017-2024 Journalism Development Network, Inc.
4
+ Copyright (c) 2025 OpenSanctions Datenbanken GmbH
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal