Skip to content

Commit 5dda3c2

Browse files
committed
feat: get taxonomy from GBIF
1 parent 5961193 commit 5dda3c2

7 files changed

Lines changed: 95 additions & 4 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ dependencies = [
2525
"typer>=0.20.0",
2626
"pygeometa @ git+https://github.com/nicokant/pygeometa@gbif-eml#egg=pygeometa",
2727
"orjson>=3.11.4",
28-
"duckdb>=1.4.3",
28+
"duckdb>=1.4.2",
2929
"lxml>=6.0.2",
3030
"openpyxl>=3.1.5",
3131
"python-calamine>=0.6.1",
@@ -41,7 +41,7 @@ dependencies = [
4141
"httpx>=0.28.1",
4242
"setuptools>=65.5.0",
4343
"backoff>=2.1.0",
44-
"pyyaml>=6.0.3",
44+
"pyyaml>=6.0.3"
4545
]
4646
description = ""
4747
license = "GPL-3.0+"

src/datasync/gbif_backbone.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from importlib.resources import files
2+
3+
import duckdb
4+
import fsspec
5+
import typer
6+
7+
from .libs.helpers import pa_read_tsv
8+
from .settings import (
9+
env,
10+
log,
11+
)
12+
13+
resources = files(__package__).joinpath("gbif_backbone")
14+
taxon_sql = resources.joinpath("taxon.sql").read_text()
15+
vernacularname_sql = resources.joinpath("vernacularname.sql").read_text()
16+
17+
18+
log.debug("Importing GBIF Backbone settings")
19+
20+
21+
GBIF_BACKBONE_DUCKDB_NAME = env.path(
22+
"GBIF_BACKBONE_DUCKDB_FILE_NAME", default="gbif_backbone.duckdb"
23+
).root
24+
GBIF_BACKBONE_URL = env.url(
25+
"GBIF_BACKBONE_URL",
26+
default="https://hosted-datasets.gbif.org/datasets/backbone/current/backbone.zip",
27+
)
28+
29+
app = typer.Typer(help="export GBIF Backbone data to DuckDB database")
30+
31+
32+
def import_taxon(conn, archive):
33+
log.debug("Importing Taxon.tsv")
34+
pa_taxon = pa_read_tsv(archive, "Taxon.tsv") # noqa: F841
35+
conn.execute(taxon_sql)
36+
37+
38+
def import_vernacular_name(conn, archive):
39+
log.debug("Importing VernacularName.tsv")
40+
pa_vernacular_names = pa_read_tsv(archive, "VernacularName.tsv") # noqa: F841
41+
conn.execute(vernacularname_sql)
42+
43+
44+
@app.command()
45+
def import_all():
46+
"""Import GBIF Backbone data into a DuckDB database."""
47+
archive = fsspec.filesystem("zip", fo=GBIF_BACKBONE_URL.geturl(), mode="r")
48+
with duckdb.connect(GBIF_BACKBONE_DUCKDB_NAME) as conn:
49+
import_taxon(conn, archive)
50+
import_vernacular_name(conn, archive)
51+
log.info("GBIF Backbone data imported successfully")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE OR REPLACE TABLE taxon AS FROM pa_taxon;
2+
CREATE INDEX taxon_taxonid ON taxon (taxonid);
3+
PRAGMA create_fts_index(
4+
'taxon', 'taxonID', 'canonicalName',
5+
overwrite=TRUE
6+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CREATE OR REPLACE TABLE vernacular_name AS
2+
SELECT DISTINCT
3+
CONCAT_WS('|', taxonid, language, vernacularname) AS vernacularid,
4+
*
5+
FROM pa_vernacular_names;
6+
CREATE INDEX vernacular_names_taxonid ON vernacular_name (taxonid);
7+
CREATE INDEX vernacular_names_language ON vernacular_name (language);
8+
PRAGMA create_fts_index(
9+
'vernacular_name', 'vernacularID', 'vernacularName',
10+
overwrite = TRUE
11+
);

src/datasync/libs/helpers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from lxml import etree
44
from lxml.etree import _Element
5+
from pyarrow import csv
56

67
PARSER: etree.XMLParser = etree.XMLParser(resolve_entities=False)
78

@@ -29,3 +30,15 @@ def get_anytext(bag: str | _Element | list[str]) -> str:
2930
# NOTE: this should never happen as the xpath evaluation always returns a list
3031
# but the type annotation is generic as xpath might return any type
3132
raise TypeError("xpath result was not a list of strings")
33+
34+
35+
TSV_PARSE_OPTIONS = csv.ParseOptions(
36+
delimiter="\t", quote_char=False, double_quote=False
37+
)
38+
39+
40+
def pa_read_tsv(archive, filename):
41+
return csv.read_csv(
42+
archive.open(filename),
43+
parse_options=TSV_PARSE_OPTIONS,
44+
)

src/datasync/main.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44

55
import typer
66

7-
from . import dms, grass, ninagen, nva, pit_registering_salmon, services, ubw
7+
from . import (
8+
dms,
9+
gbif_backbone,
10+
grass,
11+
ninagen,
12+
nva,
13+
pit_registering_salmon,
14+
services,
15+
ubw,
16+
)
817

918
app = typer.Typer(
1019
help="Provide subcommands for synchronizing different resources, see subcommands"
@@ -16,6 +25,7 @@
1625
app.add_typer(pit_registering_salmon.app, name="pit-registering-salmon")
1726
app.add_typer(grass.app, name="grass-gis")
1827
app.add_typer(services.app, name="services")
28+
app.add_typer(gbif_backbone.app, name="gbif-backbone")
1929

2030
if __name__ == "__main__":
2131
app()

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)