pycldf 2.1.0__tar.gz → 2.2.0__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.
- {pycldf-2.1.0 → pycldf-2.2.0}/CHANGELOG.md +16 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/PKG-INFO +4 -4
- {pycldf-2.1.0 → pycldf-2.2.0}/setup.cfg +5 -5
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/__init__.py +1 -1
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/dataset.py +8 -8
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/ext/discovery.py +6 -11
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/ext/markdown.py +11 -13
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/fileutil.py +2 -2
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/media.py +5 -5
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/module.py +3 -3
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/orm.py +19 -10
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/schemautil.py +4 -6
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/sliceutil.py +2 -2
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/sources.py +6 -6
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/terms.py +2 -2
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/trees.py +5 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/urlutil.py +4 -4
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/util.py +22 -12
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/validators.py +14 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/PKG-INFO +4 -4
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/SOURCES.txt +2 -1
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/requires.txt +1 -3
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_dataset.py +48 -1
- pycldf-2.2.0/tests/test_igt_validation.py +42 -0
- pycldf-2.2.0/tests/test_orm_primary_key_relations.py +45 -0
- pycldf-2.1.0/src/pycldf/_compat.py +0 -15
- {pycldf-2.1.0 → pycldf-2.2.0}/CONTRIBUTING.md +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/LICENSE +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/MANIFEST.in +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/README.md +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/RELEASING.md +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/pyproject.toml +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/setup.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/__main__.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/cli_util.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/__init__.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/catmedia.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/check.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/createdb.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/downloadmedia.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/dumpdb.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/markdown.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/splitmedia.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/stats.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/commands/validate.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/BorrowingTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/CodeTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/CognateTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/CognatesetTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/ContributionTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/EntryTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/ExampleTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/FormTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/FunctionalEquivalentTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/FunctionalEquivalentsetTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/LanguageTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/MediaTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/ParameterNetwork-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/ParameterTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/SenseTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/TreeTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/components/ValueTable-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/constraints.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/db.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/ext/__init__.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/ext/sql.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/markdown.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/Dictionary-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/Generic-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/ParallelText-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/StructureDataset-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/TextCorpus-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/modules/Wordlist-metadata.json +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/stats.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf/terms.rdf +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/dependency_links.txt +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/entry_points.txt +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/not-zip-safe +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/src/pycldf.egg-info/top_level.txt +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/test.md +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_cli.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_cli_util.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_db.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_ext_discovery.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_ext_markdown.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_ext_sql.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_media.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_misc.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_orm.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_paralleltext.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_sources.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_terms.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_trees.py +0 -0
- {pycldf-2.1.0 → pycldf-2.2.0}/tests/test_util.py +0 -0
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
The `pycldf` package adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## [2.2.0] - 2026-11-11
|
|
7
|
+
|
|
8
|
+
- Fixed bug where missing words in glossed examples would lead to downstream
|
|
9
|
+
issues (thanks @antonsoo).
|
|
10
|
+
- Fixed bug where explicit foreign key declaration was not honored when
|
|
11
|
+
resolving list-valued foreign keys (thanks @antonsoo).
|
|
12
|
+
- Fixed documentation issue with confusing error message.
|
|
13
|
+
- Enhanced support for copying datasets (thanks @antonsoo).
|
|
14
|
+
- Dropped py3.9 support.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [2.1.1] - 2026-07-17
|
|
18
|
+
|
|
19
|
+
Fixed incomplete requirements.
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [2.1.0] - 2026-07-17
|
|
7
23
|
|
|
8
24
|
- Added support for reading media file data from locally downloaded zip archives. This helps with the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pycldf
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A python library to read and write CLDF datasets
|
|
5
5
|
Home-page: https://github.com/cldf/pycldf
|
|
6
6
|
Author: Robert Forkel
|
|
@@ -15,19 +15,19 @@ Classifier: Intended Audience :: Science/Research
|
|
|
15
15
|
Classifier: Natural Language :: English
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
24
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
25
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
26
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
27
|
-
Requires-Python: >=3.
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE
|
|
30
|
-
Requires-Dist: python-frontmatter
|
|
30
|
+
Requires-Dist: python-frontmatter
|
|
31
31
|
Requires-Dist: csvw>=4.0
|
|
32
32
|
Requires-Dist: clldutils>=4.0
|
|
33
33
|
Requires-Dist: uritemplate>=3.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = pycldf
|
|
3
|
-
version = 2.
|
|
3
|
+
version = 2.2.0
|
|
4
4
|
author = Robert Forkel
|
|
5
5
|
author_email = robert_forkel@eva.mpg.de
|
|
6
6
|
description = A python library to read and write CLDF datasets
|
|
@@ -20,12 +20,12 @@ classifiers =
|
|
|
20
20
|
Natural Language :: English
|
|
21
21
|
Operating System :: OS Independent
|
|
22
22
|
Programming Language :: Python :: 3
|
|
23
|
-
Programming Language :: Python :: 3.9
|
|
24
23
|
Programming Language :: Python :: 3.10
|
|
25
24
|
Programming Language :: Python :: 3.11
|
|
26
25
|
Programming Language :: Python :: 3.12
|
|
27
26
|
Programming Language :: Python :: 3.13
|
|
28
27
|
Programming Language :: Python :: 3.14
|
|
28
|
+
Programming Language :: Python :: 3.15
|
|
29
29
|
Programming Language :: Python :: Implementation :: CPython
|
|
30
30
|
Programming Language :: Python :: Implementation :: PyPy
|
|
31
31
|
License :: OSI Approved :: Apache Software License
|
|
@@ -35,9 +35,9 @@ zip_safe = False
|
|
|
35
35
|
packages = find:
|
|
36
36
|
package_dir =
|
|
37
37
|
= src
|
|
38
|
-
python_requires = >=3.
|
|
38
|
+
python_requires = >=3.10
|
|
39
39
|
install_requires =
|
|
40
|
-
python-frontmatter
|
|
40
|
+
python-frontmatter
|
|
41
41
|
csvw>=4.0
|
|
42
42
|
clldutils>=4.0
|
|
43
43
|
uritemplate>=3.0
|
|
@@ -116,7 +116,7 @@ show_missing = true
|
|
|
116
116
|
skip_covered = true
|
|
117
117
|
|
|
118
118
|
[tox:tox]
|
|
119
|
-
envlist =
|
|
119
|
+
envlist = py310, py311, py312, py313, py314, py315
|
|
120
120
|
isolated_build = true
|
|
121
121
|
skip_missing_interpreter = true
|
|
122
122
|
|
|
@@ -3,7 +3,7 @@ An implementation of a CLDF dataset object.
|
|
|
3
3
|
"""
|
|
4
4
|
import re
|
|
5
5
|
import types
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Optional, Type, Any
|
|
7
7
|
import logging
|
|
8
8
|
import pathlib
|
|
9
9
|
import functools
|
|
@@ -39,8 +39,8 @@ __all__ = [
|
|
|
39
39
|
'TextCorpus', 'iter_datasets', 'sniff', 'SchemaError']
|
|
40
40
|
|
|
41
41
|
ORM_CLASSES = {cls.component_name(): cls for cls in orm.Object.__subclasses__()}
|
|
42
|
-
TableSpecType =
|
|
43
|
-
SchemaObjectType =
|
|
42
|
+
TableSpecType = str | Link | Table
|
|
43
|
+
SchemaObjectType = TableSpecType | tuple[TableSpecType, ColType]
|
|
44
44
|
ODict = collections.OrderedDict
|
|
45
45
|
RowType = ODict[str, Any]
|
|
46
46
|
|
|
@@ -285,7 +285,7 @@ class Dataset: # pylint: disable=too-many-public-methods
|
|
|
285
285
|
pass
|
|
286
286
|
return None
|
|
287
287
|
|
|
288
|
-
def __getitem__(self, item: SchemaObjectType) ->
|
|
288
|
+
def __getitem__(self, item: SchemaObjectType) -> csvw.Table | csvw.Column:
|
|
289
289
|
"""
|
|
290
290
|
Access to tables and columns.
|
|
291
291
|
|
|
@@ -365,7 +365,7 @@ class Dataset: # pylint: disable=too-many-public-methods
|
|
|
365
365
|
"""
|
|
366
366
|
return bool(self.get(item))
|
|
367
367
|
|
|
368
|
-
def get(self, item: SchemaObjectType, default=None) ->
|
|
368
|
+
def get(self, item: SchemaObjectType, default=None) -> csvw.Table | csvw.Column | None:
|
|
369
369
|
"""
|
|
370
370
|
Acts like `dict.get`.
|
|
371
371
|
|
|
@@ -485,7 +485,7 @@ class Dataset: # pylint: disable=too-many-public-methods
|
|
|
485
485
|
# Now remove the table:
|
|
486
486
|
self.tablegroup.tables = [t for t in self.tablegroup.tables if t.url != table.url]
|
|
487
487
|
|
|
488
|
-
def add_component(self, component:
|
|
488
|
+
def add_component(self, component: str | dict, *cols: ColSpecType, **kw) -> csvw.Table:
|
|
489
489
|
"""
|
|
490
490
|
Add a CLDF component to a dataset.
|
|
491
491
|
|
|
@@ -632,7 +632,7 @@ class Dataset: # pylint: disable=too-many-public-methods
|
|
|
632
632
|
return add_auto_constraints(self, component)
|
|
633
633
|
|
|
634
634
|
# Add data -------------------------------------------------------------------------------------
|
|
635
|
-
def add_sources(self, *sources:
|
|
635
|
+
def add_sources(self, *sources: str | Source, **kw) -> None:
|
|
636
636
|
"""
|
|
637
637
|
Add sources to the dataset.
|
|
638
638
|
|
|
@@ -676,7 +676,7 @@ class Dataset: # pylint: disable=too-many-public-methods
|
|
|
676
676
|
return row
|
|
677
677
|
raise ValueError(id_) # pragma: no cover
|
|
678
678
|
|
|
679
|
-
def get_row_url(self, table: TableType, row:
|
|
679
|
+
def get_row_url(self, table: TableType, row: RowType | str) -> Optional[str]:
|
|
680
680
|
"""
|
|
681
681
|
Get a URL associated with a row. Tables can specify associated row URLs by
|
|
682
682
|
|
|
@@ -16,7 +16,7 @@ Additional resolvers:
|
|
|
16
16
|
resolver for DOI URLs pointing to the Zenodo archive.
|
|
17
17
|
"""
|
|
18
18
|
import re
|
|
19
|
-
from typing import Optional
|
|
19
|
+
from typing import Optional
|
|
20
20
|
import pathlib
|
|
21
21
|
import zipfile
|
|
22
22
|
import warnings
|
|
@@ -29,7 +29,6 @@ from csvw.utils import is_url
|
|
|
29
29
|
|
|
30
30
|
from pycldf import Dataset, iter_datasets, sniff
|
|
31
31
|
from pycldf.urlutil import url_without_fragment
|
|
32
|
-
from pycldf._compat import entry_points_select
|
|
33
32
|
|
|
34
33
|
__all__ = ['get_dataset', 'DatasetResolver']
|
|
35
34
|
EP = 'pycldf_dataset_resolver'
|
|
@@ -47,11 +46,7 @@ class DatasetResolver: # pylint: disable=R0903
|
|
|
47
46
|
"""
|
|
48
47
|
priority = 5
|
|
49
48
|
|
|
50
|
-
def __call__(
|
|
51
|
-
self,
|
|
52
|
-
loc: str,
|
|
53
|
-
download_dir: pathlib.Path,
|
|
54
|
-
) -> Union[None, Dataset, pathlib.Path]:
|
|
49
|
+
def __call__(self, loc: str, download_dir: pathlib.Path) -> None | Dataset | pathlib.Path:
|
|
55
50
|
"""
|
|
56
51
|
:param loc: URL pointing to a place where datasets are archived.
|
|
57
52
|
:param download_dir: A directory to which resolvers can download data.
|
|
@@ -145,7 +140,7 @@ class DatasetLocator(str):
|
|
|
145
140
|
def get_resolvers() -> list[type]:
|
|
146
141
|
"""Register resolvers defined via entry points."""
|
|
147
142
|
if not _resolvers:
|
|
148
|
-
for ep in set(
|
|
143
|
+
for ep in set(entry_points().select(group=EP)):
|
|
149
144
|
try:
|
|
150
145
|
_resolvers.append(ep.load()())
|
|
151
146
|
except ImportError: # pragma: no cover
|
|
@@ -156,9 +151,9 @@ def get_resolvers() -> list[type]:
|
|
|
156
151
|
|
|
157
152
|
def _get_dataset(
|
|
158
153
|
locator: DatasetLocator,
|
|
159
|
-
location:
|
|
154
|
+
location: None | Dataset | pathlib.Path,
|
|
160
155
|
get_all: bool = False,
|
|
161
|
-
) -> Optional[
|
|
156
|
+
) -> Optional[Dataset | list[Dataset]]:
|
|
162
157
|
"""Determine whether locator matches location and if so, resolve to a Dataset instance."""
|
|
163
158
|
if isinstance(location, Dataset):
|
|
164
159
|
if locator.match(location):
|
|
@@ -180,7 +175,7 @@ def _get_dataset(
|
|
|
180
175
|
def get_dataset(locator: str,
|
|
181
176
|
download_dir: pathlib.Path,
|
|
182
177
|
base: Optional[pathlib.Path] = None,
|
|
183
|
-
get_all: bool = False) ->
|
|
178
|
+
get_all: bool = False) -> Dataset | list[Dataset]:
|
|
184
179
|
"""
|
|
185
180
|
:param locator: Dataset locator as specified in "Dataset discovery".
|
|
186
181
|
:param download_dir: Directory to which to download remote data if necessary.
|
|
@@ -4,7 +4,7 @@ This module provides tools to build a CLDF Markdown renderer.
|
|
|
4
4
|
For an example, see :class:`FilenameToComponent`.
|
|
5
5
|
"""
|
|
6
6
|
import re
|
|
7
|
-
from typing import Optional,
|
|
7
|
+
from typing import Optional, Any
|
|
8
8
|
import pathlib
|
|
9
9
|
import warnings
|
|
10
10
|
import collections
|
|
@@ -65,7 +65,7 @@ class DatasetMapping(Mapping):
|
|
|
65
65
|
if not isinstance(self.m[k], Dataset):
|
|
66
66
|
self.m[k] = get_dataset(self.m[k], download_dir, doc_path)
|
|
67
67
|
|
|
68
|
-
def __getitem__(self, prefix:
|
|
68
|
+
def __getitem__(self, prefix: str | None) -> Dataset:
|
|
69
69
|
"""
|
|
70
70
|
Get a `Dataset` mapped to a prefix.
|
|
71
71
|
"""
|
|
@@ -130,9 +130,7 @@ class CLDFMarkdownLink(MarkdownLink):
|
|
|
130
130
|
return None # pragma: no cover
|
|
131
131
|
|
|
132
132
|
def component(
|
|
133
|
-
self,
|
|
134
|
-
cldf: Optional[Union[Dataset, dict[str, Dataset], DatasetMapping]] = None,
|
|
135
|
-
) -> Union[str, None]:
|
|
133
|
+
self, cldf: None | Dataset | dict[str, Dataset] | DatasetMapping = None) -> str | None:
|
|
136
134
|
"""
|
|
137
135
|
:param cldf: `pycldf.Dataset` instance to which the link refers.
|
|
138
136
|
:return: Name of the CLDF component the link pertains to or `None`.
|
|
@@ -173,7 +171,7 @@ class CLDFMarkdownLink(MarkdownLink):
|
|
|
173
171
|
"""
|
|
174
172
|
return self.objid == '__all__'
|
|
175
173
|
|
|
176
|
-
def get_row(self, cldf:
|
|
174
|
+
def get_row(self, cldf: Dataset | DatasetMapping) -> dict:
|
|
177
175
|
"""
|
|
178
176
|
Resolve the reference in a CLDF Markdown link to a row (`dict`) in the CLDF `Dataset`.
|
|
179
177
|
"""
|
|
@@ -181,7 +179,7 @@ class CLDFMarkdownLink(MarkdownLink):
|
|
|
181
179
|
ds = DatasetMapping(cldf)[self.prefix]
|
|
182
180
|
return ds.get_row(self.component(cldf=ds), self.objid)
|
|
183
181
|
|
|
184
|
-
def get_object(self, cldf:
|
|
182
|
+
def get_object(self, cldf: Dataset | DatasetMapping) -> orm.Object:
|
|
185
183
|
"""
|
|
186
184
|
Resolve the reference in a CLDF Markdown link to an ORM object in the CLDF `Dataset`.
|
|
187
185
|
"""
|
|
@@ -219,8 +217,8 @@ class CLDFMarkdownText:
|
|
|
219
217
|
:cvar metadata_component: Name of the special "Metadata" component.
|
|
220
218
|
"""
|
|
221
219
|
def __init__(self,
|
|
222
|
-
text:
|
|
223
|
-
dataset_mapping: Optional[
|
|
220
|
+
text: pathlib.Path | str,
|
|
221
|
+
dataset_mapping: Optional[str | Dataset | dict] = None,
|
|
224
222
|
download_dir: Optional[pathlib.Path] = None):
|
|
225
223
|
"""
|
|
226
224
|
:param text: CLDF Markdown text either to be read from a path or specified as `str`.
|
|
@@ -230,7 +228,7 @@ class CLDFMarkdownText:
|
|
|
230
228
|
"""
|
|
231
229
|
p = frontmatter.loads(text) if isinstance(text, str) else frontmatter.load(str(text))
|
|
232
230
|
self.metadata: dict[str, Any] = p.metadata
|
|
233
|
-
self.dataset_mapping: Mapping[
|
|
231
|
+
self.dataset_mapping: Mapping[str | None, Dataset] = DatasetMapping(
|
|
234
232
|
p.get(DATASETS_MAPPING),
|
|
235
233
|
dataset_mapping,
|
|
236
234
|
text.parent if isinstance(text, pathlib.Path) else None,
|
|
@@ -249,7 +247,7 @@ class CLDFMarkdownText:
|
|
|
249
247
|
"""
|
|
250
248
|
return f'---\n{yaml.dump(self.metadata)}---'
|
|
251
249
|
|
|
252
|
-
def get_object(self, ml: CLDFMarkdownLink) ->
|
|
250
|
+
def get_object(self, ml: CLDFMarkdownLink) -> list | orm.Object | Source | dict:
|
|
253
251
|
"""
|
|
254
252
|
Resolve the reference in a CLDF Markdown link to the matching object from a mapped dataset.
|
|
255
253
|
|
|
@@ -279,13 +277,13 @@ class CLDFMarkdownText:
|
|
|
279
277
|
return list(self._datadict[ml.prefix][key].values()) if ml.all \
|
|
280
278
|
else self._datadict[ml.prefix][key][ml.objid]
|
|
281
279
|
|
|
282
|
-
def _render_link(self, link: CLDFMarkdownLink) ->
|
|
280
|
+
def _render_link(self, link: CLDFMarkdownLink) -> str | CLDFMarkdownLink:
|
|
283
281
|
"""Dispatches to custom rendering in case of CLDF links."""
|
|
284
282
|
if link.is_cldf_link:
|
|
285
283
|
return self.render_link(link)
|
|
286
284
|
return link
|
|
287
285
|
|
|
288
|
-
def render_link(self, cldf_link: CLDFMarkdownLink) ->
|
|
286
|
+
def render_link(self, cldf_link: CLDFMarkdownLink) -> str | CLDFMarkdownLink:
|
|
289
287
|
"""
|
|
290
288
|
CLDF Markdown renderers must implement this method.
|
|
291
289
|
"""
|
|
@@ -4,12 +4,12 @@ Functionality to access and manipulate files.
|
|
|
4
4
|
import re
|
|
5
5
|
import math
|
|
6
6
|
import string
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Optional
|
|
8
8
|
import pathlib
|
|
9
9
|
import itertools
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
PathType =
|
|
12
|
+
PathType = str | pathlib.Path
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def splitfile(p: PathType, chunksize: int, total: Optional[int] = None) -> list[pathlib.Path]:
|
|
@@ -24,7 +24,7 @@ or instantiate a :class:`.File` from a :class:`pycldf.orm.Object`:
|
|
|
24
24
|
import io
|
|
25
25
|
import json
|
|
26
26
|
import base64
|
|
27
|
-
from typing import
|
|
27
|
+
from typing import TYPE_CHECKING, Optional, Callable
|
|
28
28
|
import pathlib
|
|
29
29
|
import zipfile
|
|
30
30
|
import functools
|
|
@@ -47,7 +47,7 @@ if TYPE_CHECKING:
|
|
|
47
47
|
|
|
48
48
|
__all__ = ['Mimetype', 'MediaTable', 'File']
|
|
49
49
|
|
|
50
|
-
StrOrBytes =
|
|
50
|
+
StrOrBytes = str | bytes
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class File: # pylint: disable=too-many-instance-attributes
|
|
@@ -91,7 +91,7 @@ class File: # pylint: disable=too-many-instance-attributes
|
|
|
91
91
|
|
|
92
92
|
@classmethod
|
|
93
93
|
def from_dataset(
|
|
94
|
-
cls, ds: 'Dataset', row_or_object:
|
|
94
|
+
cls, ds: 'Dataset', row_or_object: dict | orm.Media) -> 'File':
|
|
95
95
|
"""
|
|
96
96
|
Factory method to instantiate a `File` bypassing the `Media` wrapper.
|
|
97
97
|
"""
|
|
@@ -350,7 +350,7 @@ class Mimetype:
|
|
|
350
350
|
return self.type == 'text'
|
|
351
351
|
|
|
352
352
|
@property
|
|
353
|
-
def extension(self) ->
|
|
353
|
+
def extension(self) -> None | str:
|
|
354
354
|
"""Return a suitable filename extension for the mimetype."""
|
|
355
355
|
return mimetypes.guess_extension(f'{self.type}/{self.subtype}')
|
|
356
356
|
|
|
@@ -360,7 +360,7 @@ class Mimetype:
|
|
|
360
360
|
return data.decode(self.encoding)
|
|
361
361
|
return data
|
|
362
362
|
|
|
363
|
-
def write(self, data: StrOrBytes, p: Optional[pathlib.Path] = None) ->
|
|
363
|
+
def write(self, data: StrOrBytes, p: Optional[pathlib.Path] = None) -> int | StrOrBytes:
|
|
364
364
|
"""The mimetype dictates how/if to encode data."""
|
|
365
365
|
res = data.encode(self.encoding) if self.is_text else data
|
|
366
366
|
return p.write_bytes(res) if p else res
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Functionality to manage modules, i.e. `Dataset` subclasses implementing particular CLDF modules.
|
|
3
3
|
"""
|
|
4
4
|
import dataclasses
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Optional, Type
|
|
6
6
|
|
|
7
7
|
from csvw.metadata import TableGroup
|
|
8
8
|
|
|
@@ -33,7 +33,7 @@ class Module:
|
|
|
33
33
|
"""
|
|
34
34
|
return self.uri.split('#')[1]
|
|
35
35
|
|
|
36
|
-
def match(self, thing:
|
|
36
|
+
def match(self, thing: TableGroup | str) -> bool:
|
|
37
37
|
"""Check if the module described here matches thing."""
|
|
38
38
|
if isinstance(thing, TableGroup):
|
|
39
39
|
return thing.common_props.get('dc:conformsTo') == term_uri(self.id)
|
|
@@ -45,7 +45,7 @@ class Module:
|
|
|
45
45
|
_modules = []
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def get_module_impl(base_class, spec:
|
|
48
|
+
def get_module_impl(base_class, spec: TableGroup | str) -> Optional[Type]:
|
|
49
49
|
"""
|
|
50
50
|
Returns an implementation (aka Dataset subclass) for a particular CLDF module.
|
|
51
51
|
"""
|
|
@@ -46,7 +46,7 @@ Limitations:
|
|
|
46
46
|
* ~35secs iterating over ``pycldf.Dataset.objects('ValueTable')``
|
|
47
47
|
"""
|
|
48
48
|
import types
|
|
49
|
-
from typing import TYPE_CHECKING,
|
|
49
|
+
from typing import TYPE_CHECKING, Optional, Any
|
|
50
50
|
import decimal
|
|
51
51
|
import functools
|
|
52
52
|
import collections
|
|
@@ -63,7 +63,7 @@ if TYPE_CHECKING:
|
|
|
63
63
|
from pycldf.media import File # pragma: no cover
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def to_json(s: Any) ->
|
|
66
|
+
def to_json(s: Any) -> str | float | int | None | list | dict:
|
|
67
67
|
"""Converts `s` to an object that can be serialized as JSON."""
|
|
68
68
|
if isinstance(s, (list, tuple)):
|
|
69
69
|
return [to_json(ss) for ss in s]
|
|
@@ -162,19 +162,19 @@ class Object: # pylint: disable=too-many-instance-attributes
|
|
|
162
162
|
return getattr(col, attr).expand(**variables)
|
|
163
163
|
return None # pragma: no cover
|
|
164
164
|
|
|
165
|
-
def aboutUrl(self, col: str = 'id') ->
|
|
165
|
+
def aboutUrl(self, col: str = 'id') -> str | None: # pylint: disable=invalid-name
|
|
166
166
|
"""
|
|
167
167
|
The table's `aboutUrl` property, expanded with the object's row as context.
|
|
168
168
|
"""
|
|
169
169
|
return self._expand_uritemplate('aboutUrl', col)
|
|
170
170
|
|
|
171
|
-
def valueUrl(self, col: str = 'id') ->
|
|
171
|
+
def valueUrl(self, col: str = 'id') -> str | None: # pylint: disable=invalid-name
|
|
172
172
|
"""
|
|
173
173
|
The table's `valueUrl` property, expanded with the object's row as context.
|
|
174
174
|
"""
|
|
175
175
|
return self._expand_uritemplate('valueUrl', col)
|
|
176
176
|
|
|
177
|
-
def propertyUrl(self, col: str = 'id') ->
|
|
177
|
+
def propertyUrl(self, col: str = 'id') -> str | None: # pylint: disable=invalid-name
|
|
178
178
|
"""
|
|
179
179
|
The table's `propertyUrl` property, expanded with the object's row as context.
|
|
180
180
|
"""
|
|
@@ -218,7 +218,7 @@ class Object: # pylint: disable=too-many-instance-attributes
|
|
|
218
218
|
'referencing columns other than CLDF id or primary key.')
|
|
219
219
|
return None # pragma: no cover
|
|
220
220
|
|
|
221
|
-
def all_related(self, relation: str) ->
|
|
221
|
+
def all_related(self, relation: str) -> DictTuple | list:
|
|
222
222
|
"""
|
|
223
223
|
CLDF reference properties can be list-valued. This method returns all related objects for
|
|
224
224
|
such a property.
|
|
@@ -227,7 +227,16 @@ class Object: # pylint: disable=too-many-instance-attributes
|
|
|
227
227
|
if fks and not isinstance(fks, list):
|
|
228
228
|
fks = [fks]
|
|
229
229
|
if fks:
|
|
230
|
-
|
|
230
|
+
pk = False
|
|
231
|
+
ref = self.dataset.get_foreign_key_reference(self.component_name(), relation)
|
|
232
|
+
if ref and str(ref[1].propertyUrl) != term_uri('id'):
|
|
233
|
+
if [ref[1].name] != self.dataset[TERMS[relation].references].tableSchema.primaryKey:
|
|
234
|
+
raise NotImplementedError(
|
|
235
|
+
'pycldf does not support foreign key constraints '
|
|
236
|
+
'referencing columns other than CLDF id or primary key.')
|
|
237
|
+
pk = True
|
|
238
|
+
return DictTuple(
|
|
239
|
+
self.dataset.get_object(TERMS[relation].references, fk, pk=pk) for fk in fks)
|
|
231
240
|
return []
|
|
232
241
|
|
|
233
242
|
|
|
@@ -237,7 +246,7 @@ class _WithLanguageMixin:
|
|
|
237
246
|
return self.related('languageReference')
|
|
238
247
|
|
|
239
248
|
@property
|
|
240
|
-
def languages(self) ->
|
|
249
|
+
def languages(self) -> DictTuple | list: # pylint: disable=C0116
|
|
241
250
|
return self.all_related('languageReference')
|
|
242
251
|
|
|
243
252
|
|
|
@@ -247,7 +256,7 @@ class _WithParameterMixin:
|
|
|
247
256
|
return self.related('parameterReference')
|
|
248
257
|
|
|
249
258
|
@property
|
|
250
|
-
def parameters(self) ->
|
|
259
|
+
def parameters(self) -> DictTuple | list: # pylint: disable=C0116
|
|
251
260
|
return self.all_related('parameterReference')
|
|
252
261
|
|
|
253
262
|
|
|
@@ -379,7 +388,7 @@ class Language(Object):
|
|
|
379
388
|
return None # pragma: no cover
|
|
380
389
|
|
|
381
390
|
@property
|
|
382
|
-
def as_geojson_feature(self) ->
|
|
391
|
+
def as_geojson_feature(self) -> None | dict[str, Any]:
|
|
383
392
|
"""
|
|
384
393
|
`dict` suitable for serialization as GeoJSON Feature object, with the point coordinate as
|
|
385
394
|
geographic data.
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Functionality to create schema objects.
|
|
3
3
|
"""
|
|
4
|
-
from typing import Union
|
|
5
|
-
|
|
6
4
|
from csvw.metadata import Column, Table
|
|
7
5
|
from clldutils import jsonlib
|
|
8
6
|
|
|
9
7
|
from pycldf.terms import TERMS
|
|
10
8
|
from pycldf.util import MD_SUFFIX, pkg_path
|
|
11
9
|
|
|
12
|
-
ColSpecType =
|
|
13
|
-
TableSpecType =
|
|
14
|
-
TableType =
|
|
15
|
-
ColType =
|
|
10
|
+
ColSpecType = str | dict | Column
|
|
11
|
+
TableSpecType = str | dict | Table
|
|
12
|
+
TableType = str | Table
|
|
13
|
+
ColType = str | Column
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
def make_column(spec: ColSpecType) -> Column:
|
|
@@ -5,14 +5,14 @@ In addition to Python's way of specifying slices as triples of integers (start,
|
|
|
5
5
|
allow specification as strings like '1' or '2:5', where the numbers are interpreted as **1-based**
|
|
6
6
|
indices, specifying **inclusive** boundaries. I.e. '2:5' is equivalent to `slice(1:5).`
|
|
7
7
|
"""
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import TypeVar
|
|
9
9
|
import itertools
|
|
10
10
|
from collections.abc import Sequence, Iterable
|
|
11
11
|
|
|
12
12
|
__all__ = ['multislice', 'multislice_with_split']
|
|
13
13
|
|
|
14
14
|
T = TypeVar('T')
|
|
15
|
-
SliceType =
|
|
15
|
+
SliceType = str | tuple[int] | tuple[int, int] | tuple[int, int, int] | slice
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def multislice(sliceable: Sequence[T], *slices: SliceType) -> Sequence[T]:
|
|
@@ -3,7 +3,7 @@ Functionality to handle BibTeX source data of Datasets.
|
|
|
3
3
|
"""
|
|
4
4
|
import re
|
|
5
5
|
import types
|
|
6
|
-
from typing import Optional,
|
|
6
|
+
from typing import Optional, Literal
|
|
7
7
|
import pathlib
|
|
8
8
|
import zipfile
|
|
9
9
|
import tempfile
|
|
@@ -91,7 +91,7 @@ class Source(BaseSource):
|
|
|
91
91
|
else:
|
|
92
92
|
yield database.Person(name)
|
|
93
93
|
|
|
94
|
-
def refkey(self, year_brackets:
|
|
94
|
+
def refkey(self, year_brackets: None | Literal["round", "square", "curly"] = 'round'):
|
|
95
95
|
"""Compute an author-year type reference key for the item."""
|
|
96
96
|
brackets = {
|
|
97
97
|
None: ('', ''),
|
|
@@ -205,7 +205,7 @@ class Sources:
|
|
|
205
205
|
pages = pages[:-1].strip()
|
|
206
206
|
return sid, pages
|
|
207
207
|
|
|
208
|
-
def validate(self, refs:
|
|
208
|
+
def validate(self, refs: str | list[str]) -> None:
|
|
209
209
|
"""Make sure refs are valid. If not, raises Exceptions."""
|
|
210
210
|
if not isinstance(refs, str) and any(r is None for r in refs):
|
|
211
211
|
raise ValueError('empty reference in ref list (possibly caused by trailing separator)')
|
|
@@ -232,14 +232,14 @@ class Sources:
|
|
|
232
232
|
self._add_entries(Source('misc', sid, glottolog_id=sid), **kw)
|
|
233
233
|
yield Reference(self[sid], pages)
|
|
234
234
|
|
|
235
|
-
def _add_entries(self, data:
|
|
235
|
+
def _add_entries(self, data: Source | database.BibliographyData, **kw) -> None:
|
|
236
236
|
if isinstance(data, Source):
|
|
237
237
|
entries = [(data.id, data.entry)]
|
|
238
238
|
elif hasattr(data, 'entries'):
|
|
239
239
|
entries = data.entries.items()
|
|
240
240
|
else:
|
|
241
241
|
msg = (
|
|
242
|
-
'expected `
|
|
242
|
+
'expected `pycldf.sources.Source`,'
|
|
243
243
|
' `pybtex.database.BibliographyData`,'
|
|
244
244
|
' or `simplepybtex.database.BibliographyData`;'
|
|
245
245
|
f' got {type(data)}')
|
|
@@ -302,7 +302,7 @@ class Sources:
|
|
|
302
302
|
return fname
|
|
303
303
|
return None
|
|
304
304
|
|
|
305
|
-
def add(self, *entries:
|
|
305
|
+
def add(self, *entries: str | Source, **kw) -> None:
|
|
306
306
|
"""
|
|
307
307
|
Add a source, either specified as BibTeX string or as :class:`Source`.
|
|
308
308
|
"""
|
|
@@ -8,7 +8,7 @@ import pathlib
|
|
|
8
8
|
import warnings
|
|
9
9
|
import dataclasses
|
|
10
10
|
import urllib.parse
|
|
11
|
-
from typing import Optional,
|
|
11
|
+
from typing import Optional, Callable, Any, TYPE_CHECKING, Literal, get_args
|
|
12
12
|
from collections.abc import Container
|
|
13
13
|
from xml.etree import ElementTree
|
|
14
14
|
|
|
@@ -197,7 +197,7 @@ class Terms(dict):
|
|
|
197
197
|
return {k: v for k, v in self.items() if v.subtype == 'component'}
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
def term_uri(name:
|
|
200
|
+
def term_uri(name: Term | str, terms: Container[str] = None, ns: str = URL) -> Optional[str]:
|
|
201
201
|
"""
|
|
202
202
|
Returns a full term URI associated with `name`.
|
|
203
203
|
|
|
@@ -43,6 +43,11 @@ class Tree:
|
|
|
43
43
|
"""
|
|
44
44
|
Represents a tree object as specified in a row of `TreeTable`.
|
|
45
45
|
"""
|
|
46
|
+
description: Optional[str]
|
|
47
|
+
tree_type: Optional[str]
|
|
48
|
+
tree_is_rooted: Optional[bool]
|
|
49
|
+
tree_branch_length_unit: Optional[str]
|
|
50
|
+
|
|
46
51
|
def __init__(self, trees: 'TreeTable', row: 'RowType', file: File):
|
|
47
52
|
self.row: 'RowType' = row
|
|
48
53
|
self.id: str = row[trees.cols['id'].name]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Functionality to manipulate URLs.
|
|
3
3
|
"""
|
|
4
|
-
from typing import Callable
|
|
4
|
+
from typing import Callable
|
|
5
5
|
import urllib.parse
|
|
6
6
|
|
|
7
7
|
__all__ = ['update_url', 'sanitize_url', 'url_without_fragment']
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def update_url(
|
|
11
|
-
url:
|
|
11
|
+
url: str | urllib.parse.ParseResult,
|
|
12
12
|
updater: Callable[[urllib.parse.ParseResult], tuple[str, str, str, str, str]],
|
|
13
|
-
) ->
|
|
13
|
+
) -> str | None:
|
|
14
14
|
"""Generic update function for URLs."""
|
|
15
15
|
if url is None:
|
|
16
16
|
return None
|
|
@@ -32,6 +32,6 @@ def sanitize_url(url: str) -> str:
|
|
|
32
32
|
return update_url(url, fix)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def url_without_fragment(url:
|
|
35
|
+
def url_without_fragment(url: str | urllib.parse.ParseResult) -> str:
|
|
36
36
|
"""Removes fragment from URL."""
|
|
37
37
|
return update_url(url, lambda u: (u.scheme, u.hostname, u.path, u.query, ''))
|