Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
JWCook committed Jun 24, 2024
2 parents 1dc8c54 + 64ee124 commit 59a269e
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 19 deletions.
2 changes: 1 addition & 1 deletion naturtag/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def post_init(self):
root_level=self.settings.log_level_external,
logfile=self.settings.logfile,
)
self.threadpool = ThreadPool()
self.threadpool = ThreadPool(n_worker_threads=self.settings.n_worker_threads)
self.user_dirs = UserDirs(self.settings)


Expand Down
4 changes: 3 additions & 1 deletion naturtag/app/threadpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ class ThreadPool(QThreadPool):
bar.
"""

def __init__(self, **kwargs):
def __init__(self, n_worker_threads: int = 0, **kwargs):
super().__init__(**kwargs)
self.progress = ProgressBar()
if n_worker_threads:
self.setMaxThreadCount(n_worker_threads)

def schedule(
self,
Expand Down
47 changes: 36 additions & 11 deletions naturtag/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,32 @@
from time import time
from typing import TYPE_CHECKING, Optional

from pyinaturalist import ClientSession, Observation, Photo, Taxon, WrapperPaginator, iNatClient
from pyinaturalist import (
ClientSession,
Observation,
Photo,
Taxon,
WrapperPaginator,
iNatClient,
)
from pyinaturalist.constants import MultiInt
from pyinaturalist.controllers import ObservationController, TaxonController
from pyinaturalist.converters import format_file_size
from pyinaturalist_convert.db import get_db_observations, get_db_taxa, save_observations, save_taxa
from pyinaturalist.converters import ensure_list, format_file_size
from pyinaturalist_convert.db import (
get_db_observations,
get_db_taxa,
save_observations,
save_taxa,
)
from requests_cache import SQLiteDict

from naturtag.constants import DB_PATH, DEFAULT_PAGE_SIZE, IMAGE_CACHE, ROOT_TAXON_ID, PathOrStr
from naturtag.constants import (
DB_PATH,
DEFAULT_PAGE_SIZE,
IMAGE_CACHE,
ROOT_TAXON_ID,
PathOrStr,
)

if TYPE_CHECKING:
from PySide6.QtGui import QPixmap
Expand All @@ -30,7 +49,7 @@ def __init__(self, db_path: Path = DB_PATH, **kwargs):
self.taxa = TaxonDbController(self)
self.observations = ObservationDbController(self, taxon_controller=self.taxa)

def from_ids(
def from_id(
self, observation_id: Optional[int] = None, taxon_id: Optional[int] = None
) -> Optional[Observation]:
"""Get an iNaturalist observation and/or taxon matching the specified ID(s). If only a taxon ID
Expand Down Expand Up @@ -67,11 +86,16 @@ def __init__(self, *args, taxon_controller: 'TaxonDbController', **kwargs):
self.taxon_controller = taxon_controller

def from_ids(
self, *observation_ids, refresh: bool = False, taxonomy: bool = False, **params
self,
observation_ids: MultiInt,
refresh: bool = False,
taxonomy: bool = False,
**params,
) -> WrapperPaginator[Observation]:
"""Get observations by ID; first from the database, then from the API"""
# Get any observations saved in the database (unless refreshing)
start = time()
observation_ids = ensure_list(observation_ids)
if refresh:
observations = []
else:
Expand All @@ -82,7 +106,7 @@ def from_ids(
remaining_ids = set(observation_ids) - {obs.id for obs in observations}
if remaining_ids:
logger.debug(f'Fetching remaining {len(remaining_ids)} observations from API')
api_results = super().from_ids(*remaining_ids, **params).all()
api_results = super().from_ids(remaining_ids, **params).all()
observations.extend(api_results)
self.save(api_results)

Expand Down Expand Up @@ -155,22 +179,23 @@ def save(self, observations: list[Observation]):
class TaxonDbController(TaxonController):
def from_ids(
self,
*taxon_ids: int,
taxon_ids: MultiInt,
accept_partial: bool = False,
refresh: bool = False,
**params,
) -> WrapperPaginator[Taxon]:
"""Get taxa by ID; first from the database, then from the API"""
# Get any taxa saved in the database (unless refreshing)
start = time()
taxa = [] if refresh else self._get_db_taxa(list(taxon_ids), accept_partial)
taxon_ids = ensure_list(taxon_ids)
taxa = [] if refresh else self._get_db_taxa(taxon_ids, accept_partial)
logger.debug(f'{len(taxa)} taxa found in database')

# Get remaining taxa from the API and save to the database
remaining_ids = set(taxon_ids) - {taxon.id for taxon in taxa}
if remaining_ids:
logger.debug(f'Fetching remaining {len(remaining_ids)} taxa from API')
api_results = super().from_ids(*remaining_ids, **params).all() if remaining_ids else []
api_results = super().from_ids(remaining_ids, **params).all() if remaining_ids else []
taxa.extend(api_results)
save_taxa(api_results, self.client.db_path)

Expand All @@ -193,7 +218,7 @@ def _get_taxonomy(self, taxa: list[Taxon]) -> list[Taxon]:
Besides, some may be missing and need to be fetched from the API.
"""
fetch_ids = chain.from_iterable([t.ancestor_ids + t.child_ids for t in taxa])
extended_taxa = {t.id: t for t in self.from_ids(*set(fetch_ids), accept_partial=True)}
extended_taxa = {t.id: t for t in self.from_ids(set(fetch_ids), accept_partial=True)}

for taxon in taxa:
# Depending on data source, the taxon itself may have already been added to ancestry
Expand Down
2 changes: 1 addition & 1 deletion naturtag/controllers/taxon_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def load_user_taxa(self):
def get_recent_taxa():
logger.info(f'Loading {len(display_ids)} user taxa')
return client.taxa.from_ids(
*display_ids, locale=app.settings.locale, accept_partial=True
display_ids, locale=app.settings.locale, accept_partial=True
).all()

future = app.threadpool.schedule(get_recent_taxa, priority=QThread.LowPriority)
Expand Down
4 changes: 2 additions & 2 deletions naturtag/metadata/inat_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def tag_images(
settings = settings or Settings.read()
client = client or iNatDbClient(settings.db_path)

observation = client.from_ids(observation_id, taxon_id)
observation = client.from_id(observation_id, taxon_id)
if not observation:
return []

Expand Down Expand Up @@ -141,7 +141,7 @@ def _refresh_tags(

logger.debug(f'Refreshing tags for {metadata.image_path}')
settings = settings or Settings.read()
observation = client.from_ids(metadata.observation_id, metadata.taxon_id)
observation = client.from_id(metadata.observation_id, metadata.taxon_id)
metadata = observation_to_metadata(
observation,
common_names=settings.common_names,
Expand Down
9 changes: 7 additions & 2 deletions naturtag/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,17 @@ class Settings(YamlMixin):

# iNaturalist
all_ranks: bool = doc_field(
default=False, doc='Show all available taxonomic rank filters on taxon search page'
default=False,
doc='Show all available taxonomic rank filters on taxon search page',
)
casual_observations: bool = doc_field(
default=True, doc='Include taxa from casual observations in user taxa list'
)
locale: str = doc_field(default='en', doc='Locale preference for species common names')
preferred_place_id: int = doc_field(
default=1, converter=int, doc='Place preference for regional species common names'
default=1,
converter=int,
doc='Place preference for regional species common names',
)
search_locale: bool = doc_field(
default=True, doc='Search common names for only your selected locale'
Expand All @@ -154,9 +157,11 @@ class Settings(YamlMixin):
recent_image_dirs: list[Path] = field(factory=list)
favorite_image_dirs: list[Path] = field(factory=list)

# Internal
debug: bool = field(default=False)
setup_complete: bool = field(default=False)
last_obs_check: Optional[datetime] = field(default=None)
n_worker_threads: int = field(default=1)

@classmethod
def read(cls, path: Path = CONFIG_PATH) -> 'Settings': # type: ignore
Expand Down
5 changes: 4 additions & 1 deletion packaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ This directory contains scripts and config for building Naturtag packages in the
* Taxonomy full text search database (optional download)

# Release steps
* Locally build full taxonomy FTS database (See: https://pyinaturalist-convert.readthedocs.io/en/stable/modules/fts.html)
* Locally build full taxonomy FTS database with `build_taxon_db.py`. For reference, see:
* [pyinaturalist_convert.dwca](https://pyinaturalist-convert.readthedocs.io/en/stable/modules/dwca.html)
* [pyinaturalist_convert.taxonomy](https://pyinaturalist-convert.readthedocs.io/en/stable/modules/taxonomy.html)
* [pyinaturalist_convert.fts](https://pyinaturalist-convert.readthedocs.io/en/stable/modules/fts.html)
* Export subset of db (English language, common species only) with `export_taxa.sh`
* Commit to repo under `assets/data/taxonomy.tar.gz`
* Update version in `pyproject.toml` and Actual Installer config (`naturtag.aip`)
Expand Down
12 changes: 12 additions & 0 deletions packaging/build_taxon_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python
from pyinaturalist_convert import (
aggregate_taxon_db,
enable_logging,
load_dwca_tables,
load_fts_taxa,
)

enable_logging('DEBUG')
load_dwca_tables()
aggregate_taxon_db()
load_fts_taxa(language='all')

0 comments on commit 59a269e

Please sign in to comment.