diff --git a/examples/Data Visualizations - Seaborn.ipynb b/examples/Data Visualizations - Seaborn.ipynb index 9d0f951c..0be2879f 100644 --- a/examples/Data Visualizations - Seaborn.ipynb +++ b/examples/Data Visualizations - Seaborn.ipynb @@ -12,7 +12,7 @@ "metadata": {}, "source": [ "# Seaborn examples\n", - "Here are some examples of visualizations that can be created using [Seaborn](seaborn.pydata.org)." + "Here are some examples of visualizations that can be created using [Seaborn](https://seaborn.pydata.org/)." ] }, { diff --git a/pyinaturalist/api_docs/model_docs.py b/pyinaturalist/api_docs/model_docs.py index de2fbb8b..8470e531 100644 --- a/pyinaturalist/api_docs/model_docs.py +++ b/pyinaturalist/api_docs/model_docs.py @@ -39,12 +39,10 @@ def get_model_classes() -> List[Type]: return model_classes -# TODO: Also include regular @properties? -# TODO: CSS to style LazyProperties with a different background color? -# TODO: Remove autodoc member docs for LazyProperties def get_model_doc(cls: Type) -> List[Tuple[str, str, str]]: """Get the name, type and description for all model attributes, properties, and LazyProperties. - If an attribute has a validator with options, include those options in the description. + If an attribute has metadata for options (possible values for the attribute), include those + options in the description. """ doc_rows = [_get_field_doc(field) for field in cls.__attrs_attrs__ if not field.name.startswith('_')] @@ -70,8 +68,9 @@ def _get_field_doc(field: Attribute) -> Tuple[str, str, str]: """Get a row documenting an attrs Attribute""" rtype = format_annotation(field.type) doc = field.metadata.get('doc', '') - if getattr(field.validator, 'options', None): - options = ', '.join([f'``{opt}``' for opt in field.validator.options if opt is not None]) + options = field.metadata.get('options', []) + if options: + options = ', '.join([f'``{opt}``' for opt in filter(None, options)]) doc += f'\n\n**Options:** {options}' return (f'**{field.name}**', rtype, doc) diff --git a/pyinaturalist/constants.py b/pyinaturalist/constants.py index 3a262b76..04528cb9 100644 --- a/pyinaturalist/constants.py +++ b/pyinaturalist/constants.py @@ -130,7 +130,7 @@ ID_CATEGORIES = ['improving', 'supporting', 'leading', 'maverick'] ORDER_DIRECTIONS = ['asc', 'desc'] PLACE_CATEGORIES = ['standard', 'community'] -PROJECT_TYPES = ['collection', 'umbrella'] +PROJECT_TYPES = ['assessment', 'bioblitz', 'collection', 'umbrella'] QUALITY_GRADES = ['casual', 'needs_id', 'research'] SEARCH_PROPERTIES = ['names', 'tags', 'description', 'place'] SOURCES = ['places', 'projects', 'taxa', 'users'] diff --git a/pyinaturalist/formatters.py b/pyinaturalist/formatters.py index ae40616e..7b8695ef 100644 --- a/pyinaturalist/formatters.py +++ b/pyinaturalist/formatters.py @@ -199,7 +199,6 @@ def _format_model_objects(obj: ResponseOrResults, cls: Type[BaseModel], **kwargs return '\n'.join([str(obj) for obj in objects]) -# TODO: Figure out type annotations for these. Or just replace with pprint()? format_controlled_terms = partial(_format_model_objects, cls=ControlledTerm) format_identifications = partial(_format_model_objects, cls=Identification) format_observations = partial(_format_model_objects, cls=Observation) diff --git a/pyinaturalist/models/__init__.py b/pyinaturalist/models/__init__.py index 243ac385..e34a3676 100644 --- a/pyinaturalist/models/__init__.py +++ b/pyinaturalist/models/__init__.py @@ -27,10 +27,11 @@ define_model_collection: Callable = define(auto_attribs=False, order=False, slots=False) -def field(doc: str = '', metadata: Dict = None, **kwargs): - """A field with extra documentation metadata""" +def field(doc: str = '', options: Iterable = None, metadata: Dict = None, **kwargs): + """A field with extra metadata for documentation and options""" metadata = metadata or {} metadata['doc'] = doc + metadata['options'] = options return attr.field(**kwargs, metadata=metadata) diff --git a/pyinaturalist/models/identification.py b/pyinaturalist/models/identification.py index 3c293441..9b281725 100644 --- a/pyinaturalist/models/identification.py +++ b/pyinaturalist/models/identification.py @@ -9,7 +9,6 @@ datetime_now_field, define_model, field, - is_in, ) @@ -20,7 +19,7 @@ class Identification(BaseModel): """ body: str = field(default=None, doc='Comment text') - category: str = field(default=None, validator=is_in(ID_CATEGORIES), doc='Identification category') + category: str = field(default=None, options=ID_CATEGORIES, doc='Identification category') created_at: datetime = datetime_now_field(doc='Date and time the identification was added') current: bool = field( default=None, doc='Indicates if the identification is the currently accepted one' diff --git a/pyinaturalist/models/observation.py b/pyinaturalist/models/observation.py index edada814..b97c7dcd 100644 --- a/pyinaturalist/models/observation.py +++ b/pyinaturalist/models/observation.py @@ -1,3 +1,4 @@ +# TODO: Possible models for faves, sounds, and votes? from datetime import datetime from typing import Any, Dict, List, Optional @@ -30,7 +31,6 @@ datetime_now_field, define_model_collection, field, - is_in, upper, ) from pyinaturalist.v1 import get_observation @@ -48,9 +48,8 @@ class Observation(BaseModel): ) community_taxon_id: int = field(default=None, doc='The current community identification taxon') description: str = field(default=None, doc='Observation description') - geoprivacy: str = field( - default=None, validator=is_in(GEOPRIVACY_LEVELS), doc='Location privacy level' - ) + faves: List[Dict] = field(factory=list, doc='Details on users who have favorited the observation') + geoprivacy: str = field(default=None, options=GEOPRIVACY_LEVELS, doc='Location privacy level') identifications_count: int = field(default=None, doc='Total number of identifications') identifications_most_agree: bool = field(default=None, doc='Indicates if most identifications agree') identifications_most_disagree: bool = field( @@ -58,10 +57,7 @@ class Observation(BaseModel): ) identifications_some_agree: bool = field(default=None, doc='Indicates if some identifications agree') license_code: str = field( - default=None, - converter=upper, - validator=is_in(ALL_LICENSES), - doc='Creative Commons license code', + default=None, converter=upper, options=ALL_LICENSES, doc='Creative Commons license code' ) location: Coordinates = coordinate_pair() mappable: bool = field(default=None, doc='Indicates if the observation can be shown on a map') @@ -74,38 +70,22 @@ class Observation(BaseModel): default=None, doc='Indicates if coordinates are obscured (showing a broad approximate location on the map)', ) + observed_on: DateTime = datetime_field(doc='Date and time the organism was observed') + outlinks: List[Dict] = field( + factory=list, doc='Linked observation pages on other sites (e.g., GBIF)' + ) out_of_range: bool = field( default=None, doc='Indicates if the taxon is observed outside of its known range' ) owners_identification_from_vision: bool = field( default=None, doc="Indicates if the owner's ID was selected from computer vision results" ) + place_guess: str = field(default=None, doc='Place name determined from observation coordinates') + place_ids: List[int] = field(factory=list) positional_accuracy: int = field( default=None, doc='GPS accuracy in meters (real accuracy, if obscured)' ) - public_positional_accuracy: int = field( - default=None, doc='GPS accuracy in meters (not accurate if obscured)' - ) - quality_grade: str = field(default=None, validator=is_in(QUALITY_GRADES), doc='Quality grade') - site_id: int = field( - default=None, doc='Site ID for iNaturalist network members, or ``1`` for inaturalist.org' - ) - species_guess: str = field(default=None, doc="Taxon name from observer's initial identification") - observed_on: DateTime = datetime_field(doc='Date and time the organism was observed') - updated_at: DateTime = datetime_field(doc='Date and time the observation was last updated') - uri: str = field(default=None, doc='Link to observation details page') - uuid: str = field( - default=None, doc='Universally unique ID; generally preferred over ``id`` where possible' - ) - - # Nested collections - # TODO: Possible models for faves, sounds, and votes? - faves: List[Dict] = field(factory=list, doc='Details on users who have favorited the observation') - outlinks: List[Dict] = field( - factory=list, doc='Linked observation pages on other sites (e.g., GBIF)' - ) - place_ids: List[int] = field(factory=list) preferences: Dict[str, Any] = field( factory=dict, doc='Any user observation preferences (related to community IDs, coordinate access, etc.)', @@ -117,10 +97,23 @@ class Observation(BaseModel): project_ids_without_curator_id: List[int] = field( factory=list, doc='Project IDs without curator identification for this observation' ) + public_positional_accuracy: int = field( + default=None, doc='GPS accuracy in meters (not accurate if obscured)' + ) + quality_grade: str = field(default=None, options=QUALITY_GRADES, doc='Quality grade') quality_metrics: List[Dict] = field(factory=list, doc='Data quality assessment metrics') reviewed_by: List[int] = field(factory=list, doc='IDs of users who have reviewed the observation') + site_id: int = field( + default=None, doc='Site ID for iNaturalist network members, or ``1`` for inaturalist.org' + ) sounds: List[Dict] = field(factory=list, doc='Observation sound files') + species_guess: str = field(default=None, doc="Taxon name from observer's initial identification") tags: List[str] = field(factory=list, doc='Arbitrary user tags added to the observation') + updated_at: DateTime = datetime_field(doc='Date and time the observation was last updated') + uri: str = field(default=None, doc='Link to observation details page') + uuid: str = field( + default=None, doc='Universally unique ID; generally preferred over ``id`` where possible' + ) votes: List[Dict] = field(factory=list, doc='Votes on data quality assessment metrics') # Lazy-loaded model objects diff --git a/pyinaturalist/models/photo.py b/pyinaturalist/models/photo.py index 6a560634..c502fc88 100644 --- a/pyinaturalist/models/photo.py +++ b/pyinaturalist/models/photo.py @@ -1,9 +1,8 @@ -# TODO: Method to preview image in Jupyter from typing import Optional, Tuple from pyinaturalist.constants import ALL_LICENSES, CC_LICENSES, PHOTO_INFO_BASE_URL, PHOTO_SIZES, TableRow from pyinaturalist.converters import format_dimensions, format_license -from pyinaturalist.models import BaseModel, define_model, field, is_in +from pyinaturalist.models import BaseModel, define_model, field @define_model @@ -14,10 +13,7 @@ class Photo(BaseModel): attribution: str = field(default=None, doc='License attribution') license_code: str = field( - default=None, - converter=format_license, - validator=is_in(ALL_LICENSES), - doc='Creative Commons license code', + default=None, converter=format_license, options=ALL_LICENSES, doc='Creative Commons license code' ) original_dimensions: Tuple[int, int] = field( converter=format_dimensions, default=(0, 0), doc='Dimensions of original image' diff --git a/pyinaturalist/models/project.py b/pyinaturalist/models/project.py index 7da8d9b8..5aa77d66 100644 --- a/pyinaturalist/models/project.py +++ b/pyinaturalist/models/project.py @@ -1,7 +1,14 @@ from datetime import datetime from typing import Dict, List -from pyinaturalist.constants import INAT_BASE_URL, Coordinates, DateTime, JsonResponse, TableRow +from pyinaturalist.constants import ( + INAT_BASE_URL, + PROJECT_TYPES, + Coordinates, + DateTime, + JsonResponse, + TableRow, +) from pyinaturalist.models import ( BaseModel, LazyProperty, @@ -81,21 +88,22 @@ class Project(BaseModel): doc='Indicates if this is an umbrella project (containing observations from other projects)', ) location: Coordinates = coordinate_pair() - place_id: int = field(default=None) - prefers_user_trust: bool = field(default=None) - project_type: str = field(default=None) # Enum - slug: str = field(default=None, doc='URL slug') - terms: str = field(default=None, doc='Project terms') - title: str = field(default=None, doc='Project title') - updated_at: DateTime = datetime_field(doc='Date and time the project was last updated') - - # Nested collections - project_observation_rules: List[Dict] = field(factory=list) + place_id: int = field(default=None, doc='Project place ID') + prefers_user_trust: bool = field( + default=None, + doc='Indicates if the project wants users to share hidden coordinates with the project admins', + ) + project_observation_rules: List[Dict] = field(factory=list, doc='Project observation rules') + project_type: str = field(default=None, options=PROJECT_TYPES, doc='Project type') # Enum rule_preferences: List[Dict] = field(factory=list) search_parameters: List[Dict] = field(factory=list, doc='Filters for observations to include') site_features: List[Dict] = field( factory=list, doc='Details about if/when the project was featured on inaturalist.org' ) + slug: str = field(default=None, doc='URL slug') + terms: str = field(default=None, doc='Project terms') + title: str = field(default=None, doc='Project title') + updated_at: DateTime = datetime_field(doc='Date and time the project was last updated') user_ids: List[int] = field(factory=list) # Lazy-loaded model objects diff --git a/pyinaturalist/models/search.py b/pyinaturalist/models/search.py index 93202ec5..1b286d40 100644 --- a/pyinaturalist/models/search.py +++ b/pyinaturalist/models/search.py @@ -1,7 +1,7 @@ from typing import List, Union from pyinaturalist.constants import TableRow -from pyinaturalist.models import BaseModel, Place, Project, Taxon, User, define_model, field, is_in +from pyinaturalist.models import BaseModel, Place, Project, Taxon, User, define_model, field SEARCH_RESULT_TYPES = {cls.__name__: cls for cls in [Place, Project, Taxon, User]} SEARCH_RESULT_TITLES = {'Place': 'name', 'Project': 'title', 'Taxon': 'full_name', 'User': 'login'} @@ -15,7 +15,7 @@ class SearchResult(BaseModel): """ score: float = field(default=0, doc='Search result rank') - type: str = field(default=None, validator=is_in(SEARCH_RESULT_TYPES), doc='Search result type') + type: str = field(default=None, options=SEARCH_RESULT_TYPES, doc='Search result type') matches: List[str] = field(factory=list, doc='Search terms matched') record: SearchResultRecord = field(default=None, doc='Search result object') diff --git a/pyinaturalist/models/taxon.py b/pyinaturalist/models/taxon.py index 0599b425..1c406221 100644 --- a/pyinaturalist/models/taxon.py +++ b/pyinaturalist/models/taxon.py @@ -24,7 +24,6 @@ define_model, define_model_collection, field, - is_in, upper, ) from pyinaturalist.v1 import get_taxa_by_id @@ -52,7 +51,7 @@ class ConservationStatus(BaseModel): status: str = field( default=None, converter=upper, - validator=is_in(CONSERVATION_STATUSES), + options=CONSERVATION_STATUSES, doc='Short code for conservation status', ) status_name: str = field(default=None, doc='Full name of conservation status') @@ -78,7 +77,7 @@ class EstablishmentMeans(BaseModel): """The establishment means for a taxon in a given location""" establishment_means: str = field( - default=None, validator=is_in(ESTABLISTMENT_MEANS), doc='Establishment means description' + default=None, options=ESTABLISTMENT_MEANS, doc='Establishment means description' ) place: property = LazyProperty( Place.from_json_list, type=Place, doc='Location that the establishment means applies to' @@ -97,6 +96,9 @@ class Taxon(BaseModel): include nested ``ancestors``, ``children``, and results from :py:func:`.get_taxa_autocomplete`. """ + ancestor_ids: List[int] = field( + factory=list, doc='Taxon IDs of ancestors, from highest rank to lowest' + ) complete_rank: str = field( default=None, doc='Complete or "leaf taxon" rank, e.g. species or subspecies' ) @@ -104,6 +106,9 @@ class Taxon(BaseModel): default=None, doc='Total number of species descended from this taxon' ) created_at: DateTime = datetime_field(doc='Date and time the taxon was added to iNaturalist') + current_synonymous_taxon_ids: List[int] = field( + factory=list, doc='Taxon IDs of taxa that are accepted synonyms' + ) extinct: bool = field(default=None, doc='Indicates if the taxon is extinct') iconic_taxon_id: int = field( default=0, doc='ID of the iconic taxon (e.g., general taxon "category")' @@ -114,12 +119,17 @@ class Taxon(BaseModel): is_active: bool = field( default=None, doc='Indicates if the taxon is active (and not renamed, moved, etc.)' ) + listed_taxa: List[int] = field(factory=list, doc='Listed taxon IDs') listed_taxa_count: int = field( default=None, doc='Number of listed taxa from this taxon + descendants' ) + matched_term: str = field(default=None, doc='Matched search term, from autocomplete results') name: str = field( default=None, doc='Taxon name; contains full scientific name at species level and below' ) + names: List[Dict] = field( + factory=list, doc='All regional common names; only returned if ``all_names`` is specified' + ) observations_count: int = field( default=None, doc='Total number of observations of this taxon and its descendants' ) @@ -132,7 +142,7 @@ class Taxon(BaseModel): default=None, doc='Number indicating rank level, for easier comparison between ranks (kingdom=higest)', ) - rank: str = field(default=None, validator=is_in(RANKS), doc='Taxon rank') + rank: str = field(default=None, options=RANKS, doc='Taxon rank') taxon_changes_count: int = field(default=None, doc='Number of curator changes to this taxon') taxon_schemes_count: int = field(default=None, doc='Taxon schemes that include this taxon') wikipedia_summary: str = field( @@ -140,16 +150,7 @@ class Taxon(BaseModel): ) wikipedia_url: str = field(default=None, doc='URL to Wikipedia article for the taxon, if available') - # Nested collections - ancestor_ids: List[int] = field( - factory=list, doc='Taxon IDs of ancestors, from highest rank to lowest' - ) - current_synonymous_taxon_ids: List[int] = field( - factory=list, doc='Taxon IDs of taxa that are accepted synonyms' - ) - listed_taxa: List[int] = field(factory=list, doc='Listed taxon IDs') - - # Lazy-loade model objects + # Lazy-loaded model objects ancestors: property = LazyProperty(BaseModel.from_json_list) children: property = LazyProperty(BaseModel.from_json_list) conservation_status: property = LazyProperty( diff --git a/pyinaturalist/node_api.py b/pyinaturalist/node_api.py index 6966b948..9ee7100d 100644 --- a/pyinaturalist/node_api.py +++ b/pyinaturalist/node_api.py @@ -19,7 +19,6 @@ def get_all_observations(**params) -> List[JsonResponse]: return paginate_all(get_observations, method='id', **params)['results'] -# TODO: Deprecate get_geojson_observations and move to pyinaturalist-convert def get_geojson_observations(properties: List[str] = None, **params) -> JsonResponse: """Get all observation results combined into a GeoJSON ``FeatureCollection``. By default this includes some basic observation properties as GeoJSON ``Feature`` properties. diff --git a/pyinaturalist/request_params.py b/pyinaturalist/request_params.py index 34f5117d..3d8b5dfe 100644 --- a/pyinaturalist/request_params.py +++ b/pyinaturalist/request_params.py @@ -2,7 +2,6 @@ The main purpose of these functions is to support some python-specific conveniences and translate them into standard request parameters, along with request validation that makes debugging easier. """ -# TODO: It would be nice to put all the multiple-choice options on the models and use attrs validators from datetime import date, datetime from dateutil.relativedelta import relativedelta from logging import getLogger diff --git a/pyinaturalist/v0/observations.py b/pyinaturalist/v0/observations.py index 49601d4c..7873d88c 100644 --- a/pyinaturalist/v0/observations.py +++ b/pyinaturalist/v0/observations.py @@ -1,4 +1,3 @@ -# TODO: Decide on consistent terminology for POST/PUT endpoints, rename, and add DeprecationWarnings to previous names from logging import getLogger from typing import List, Union diff --git a/test/sample_data.py b/test/sample_data.py index 4f1e5999..01a005a7 100644 --- a/test/sample_data.py +++ b/test/sample_data.py @@ -41,6 +41,8 @@ def load_all_sample_data() -> Dict[str, Dict]: j_taxon_4_preferred_place = SAMPLE_DATA['get_taxa_with_preferred_place']['results'][0] j_taxon_5_cs_status = j_observation_2['taxon'] j_taxon_6_cs_statuses = SAMPLE_DATA['get_taxa_by_id_conservation_statuses']['results'][0] +j_taxon_7_autocomplete = SAMPLE_DATA['get_taxa_autocomplete']['results'][0] +j_taxon_8_all_names = SAMPLE_DATA['get_taxa_with_all_names']['results'][0] j_annotation_1 = j_observation_3_ofvs['annotations'][0] j_comments = j_observation_2['comments'] diff --git a/test/sample_data/get_taxa_with_all_names.json b/test/sample_data/get_taxa_with_all_names.json new file mode 100644 index 00000000..d0f712ab --- /dev/null +++ b/test/sample_data/get_taxa_with_all_names.json @@ -0,0 +1,484 @@ +{ + "total_results": 5, + "page": 1, + "per_page": 30, + "results": [ + { + "observations_count": 65515, + "taxon_schemes_count": 9, + "ancestry": "48460/1/2/355675/3/7251/7823/7998", + "is_active": true, + "flag_counts": { + "unresolved": 0, + "resolved": 1 + }, + "wikipedia_url": "http://en.wikipedia.org/wiki/American_crow", + "current_synonymous_taxon_ids": null, + "iconic_taxon_id": 3, + "rank_level": 10, + "taxon_changes_count": 0, + "names": [ + { + "is_valid": true, + "name": "Corvus brachyrhynchos", + "position": 0, + "locale": "sci" + }, + { + "is_valid": true, + "name": "American Crow", + "position": 0, + "locale": "en" + }, + { + "is_valid": true, + "name": "Cuervo Norteamericano", + "position": 0, + "locale": "es" + }, + { + "is_valid": true, + "name": "Cuervo americano", + "position": 1, + "locale": "es" + }, + { + "is_valid": true, + "name": "AMCR", + "position": 2, + "locale": "und" + }, + { + "is_valid": true, + "name": "corneille d'Am\u00e9rique", + "position": 3, + "locale": "fr" + }, + { + "is_valid": true, + "name": "amerikanvaris", + "position": 4, + "locale": "fi" + }, + { + "is_valid": true, + "name": "Corvo-americano", + "position": 6, + "locale": "pt" + }, + { + "is_valid": true, + "name": "'Alal\u0101", + "position": 9, + "locale": "haw" + }, + { + "is_valid": true, + "name": "Amerikansk Krage", + "position": 10, + "locale": "da" + }, + { + "is_valid": true, + "name": "Amerikaanse Kraai", + "position": 11, + "locale": "nl" + }, + { + "is_valid": true, + "name": "Amerikansk Kr\u00e5ka", + "position": 12, + "locale": "sv" + }, + { + "is_valid": true, + "name": "Ameerika vares", + "position": 13, + "locale": "et" + }, + { + "is_valid": true, + "name": "Amerikanerkr\u00e4he", + "position": 14, + "locale": "de" + }, + { + "is_valid": true, + "name": "\u30a2\u30e1\u30ea\u30ab\u30ac\u30e9\u30b9", + "position": 15, + "locale": "ja" + }, + { + "is_valid": true, + "name": "\u0410\u043c\u0435\u0440\u0438\u043a\u0430\u043d\u0441\u043a\u0438\u0439 \u0432\u043e\u0440\u043e\u043d", + "position": 16, + "locale": "ru" + }, + { + "is_valid": true, + "name": "R\u00f6vidcs\u0151r\u0171 varj\u00fa", + "position": 17, + "locale": "und" + }, + { + "is_valid": true, + "name": "vr\u00e1na americk\u00e1", + "position": 18, + "locale": "cs" + }, + { + "is_valid": true, + "name": "\u0391\u03bc\u03b5\u03c1\u03b9\u03ba\u03ac\u03bd\u03b9\u03ba\u03b7 \u039c\u03b1\u03c5\u03c1\u03bf\u03ba\u03bf\u03c5\u03c1\u03bf\u03cd\u03bd\u03b1", + "position": 19, + "locale": "el" + }, + { + "is_valid": true, + "name": "ka'qawej", + "position": 20, + "locale": "und" + }, + { + "is_valid": true, + "name": "Lar\u011dbeka korvo", + "position": 21, + "locale": "eo" + }, + { + "is_valid": true, + "name": "fitheach", + "position": 23, + "locale": "und" + }, + { + "is_valid": true, + "name": "Wrona ameryka\u0144ska", + "position": 24, + "locale": "pl" + }, + { + "is_valid": true, + "name": "G\u00e1\u2019ga:\u2019", + "position": 25, + "locale": "und" + }, + { + "is_valid": true, + "name": "\u77ed\u5634\u9e26", + "position": 26, + "locale": "zh-CN" + }, + { + "is_valid": true, + "name": "cornacchia americana", + "position": 27, + "locale": "it" + }, + { + "is_valid": true, + "name": "Common Crow", + "position": 28, + "locale": "en" + }, + { + "is_valid": true, + "name": "Amerikan Kargas\u0131", + "position": 29, + "locale": "tr" + }, + { + "is_valid": false, + "name": "Corvus brachyrynchos", + "position": 0, + "locale": "sci" + }, + { + "is_valid": false, + "name": "Aandeg", + "position": 22, + "locale": "oj" + } + ], + "atlas_id": null, + "complete_species_count": null, + "parent_id": 7998, + "complete_rank": "subspecies", + "name": "Corvus brachyrhynchos", + "rank": "species", + "extinct": false, + "id": 8021, + "default_photo": { + "square_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/24115/square.jpg?1545407104", + "attribution": "(c) Joe McKenna, certains droits r\u00e9serv\u00e9s (CC BY-NC)", + "flags": [], + "medium_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/24115/medium.jpg?1545407104", + "id": 24115, + "license_code": "cc-by-nc", + "original_dimensions": { + "width": 2048, + "height": 1448 + }, + "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/24115/square.jpg?1545407104" + }, + "ancestor_ids": [ + 48460, + 1, + 2, + 355675, + 3, + 7251, + 7823, + 7998, + 8021 + ], + "iconic_taxon_name": "Aves", + "preferred_common_name": "American Crow" + }, + { + "observations_count": 10, + "taxon_schemes_count": 1, + "ancestry": "48460/1/2/355675/3/7251/7823/7998/8021", + "is_active": true, + "flag_counts": { + "unresolved": 0, + "resolved": 0 + }, + "wikipedia_url": null, + "current_synonymous_taxon_ids": null, + "iconic_taxon_id": 3, + "rank_level": 5, + "taxon_changes_count": 0, + "names": [ + { + "is_valid": true, + "name": "Corvus brachyrhynchos brachyrhynchos", + "position": 0, + "locale": "sci" + }, + { + "is_valid": true, + "name": "Amerikaanse Kraai ssp brachyrhynchos", + "position": 1, + "locale": "nl" + }, + { + "is_valid": true, + "name": "Eastern Crow", + "position": 2, + "locale": "en" + } + ], + "atlas_id": null, + "complete_species_count": null, + "parent_id": 8021, + "name": "Corvus brachyrhynchos brachyrhynchos", + "rank": "subspecies", + "extinct": false, + "id": 721973, + "default_photo": { + "square_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/32119438/square.jpeg?1551338317", + "attribution": "(c) Sarah Headley, some rights reserved (CC BY-NC)", + "flags": [], + "medium_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/32119438/medium.jpeg?1551338317", + "id": 32119438, + "license_code": "cc-by-nc", + "original_dimensions": { + "width": 1536, + "height": 2048 + }, + "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/32119438/square.jpeg?1551338317" + }, + "ancestor_ids": [ + 48460, + 1, + 2, + 355675, + 3, + 7251, + 7823, + 7998, + 8021, + 721973 + ], + "iconic_taxon_name": "Aves", + "preferred_common_name": "Eastern Crow" + }, + { + "observations_count": 3, + "taxon_schemes_count": 1, + "ancestry": "48460/1/2/355675/3/7251/7823/7998/8021", + "is_active": true, + "flag_counts": { + "unresolved": 0, + "resolved": 0 + }, + "wikipedia_url": null, + "current_synonymous_taxon_ids": null, + "iconic_taxon_id": 3, + "rank_level": 5, + "taxon_changes_count": 0, + "names": [ + { + "is_valid": true, + "name": "Corvus brachyrhynchos pascuus", + "position": 0, + "locale": "sci" + }, + { + "is_valid": true, + "name": "Amerikaanse Kraai ssp pascuus", + "position": 1, + "locale": "nl" + }, + { + "is_valid": true, + "name": "Florida Crow", + "position": 2, + "locale": "en" + } + ], + "atlas_id": null, + "complete_species_count": null, + "parent_id": 8021, + "name": "Corvus brachyrhynchos pascuus", + "rank": "subspecies", + "extinct": false, + "id": 721975, + "default_photo": { + "square_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/64839618/square.jpeg?1585417642", + "attribution": "(c) Andrew Block, some rights reserved (CC BY-NC)", + "flags": [], + "medium_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/64839618/medium.jpeg?1585417642", + "id": 64839618, + "license_code": "cc-by-nc", + "original_dimensions": { + "width": 1991, + "height": 2048 + }, + "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/64839618/square.jpeg?1585417642" + }, + "ancestor_ids": [ + 48460, + 1, + 2, + 355675, + 3, + 7251, + 7823, + 7998, + 8021, + 721975 + ], + "iconic_taxon_name": "Aves", + "preferred_common_name": "Florida Crow" + }, + { + "observations_count": 2, + "taxon_schemes_count": 1, + "ancestry": "48460/1/2/355675/3/7251/7823/7998/8021", + "is_active": true, + "flag_counts": { + "unresolved": 0, + "resolved": 0 + }, + "wikipedia_url": null, + "current_synonymous_taxon_ids": null, + "iconic_taxon_id": 3, + "rank_level": 5, + "taxon_changes_count": 0, + "names": [ + { + "is_valid": true, + "name": "Corvus brachyrhynchos paulus", + "position": 0, + "locale": "sci" + }, + { + "is_valid": true, + "name": "Southern Crow", + "position": 1, + "locale": "en" + } + ], + "atlas_id": null, + "complete_species_count": null, + "parent_id": 8021, + "name": "Corvus brachyrhynchos paulus", + "rank": "subspecies", + "extinct": false, + "id": 721976, + "default_photo": null, + "ancestor_ids": [ + 48460, + 1, + 2, + 355675, + 3, + 7251, + 7823, + 7998, + 8021, + 721976 + ], + "iconic_taxon_name": "Aves", + "preferred_common_name": "Southern Crow" + }, + { + "observations_count": 0, + "taxon_schemes_count": 1, + "ancestry": "48460/1/2/355675/3/7251/7823/7998/8021", + "is_active": true, + "flag_counts": { + "unresolved": 0, + "resolved": 0 + }, + "wikipedia_url": null, + "current_synonymous_taxon_ids": null, + "iconic_taxon_id": 3, + "rank_level": 5, + "taxon_changes_count": 0, + "names": [ + { + "is_valid": true, + "name": "Corvus brachyrhynchos hesperis", + "position": 0, + "locale": "sci" + }, + { + "is_valid": true, + "name": "Amerikaanse Kraai ssp hesperis", + "position": 1, + "locale": "nl" + }, + { + "is_valid": true, + "name": "Western Crow", + "position": 2, + "locale": "en" + } + ], + "atlas_id": null, + "complete_species_count": null, + "parent_id": 8021, + "name": "Corvus brachyrhynchos hesperis", + "rank": "subspecies", + "extinct": false, + "id": 721974, + "default_photo": null, + "ancestor_ids": [ + 48460, + 1, + 2, + 355675, + 3, + 7251, + 7823, + 7998, + 8021, + 721974 + ], + "iconic_taxon_name": "Aves", + "preferred_common_name": "Western Crow" + } + ] +} \ No newline at end of file diff --git a/test/test_models.py b/test/test_models.py index 9286312e..5cd5093f 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -437,6 +437,16 @@ def test_taxon__empty(): assert taxon.taxon_photos == [] +def test_taxon__all_names(): + taxon = Taxon.from_json(j_taxon_8_all_names) + assert taxon.names[1] == {'is_valid': True, 'name': 'American Crow', 'position': 0, 'locale': 'en'} + + +def test_taxon__autocomplete(): + taxon = Taxon.from_json(j_taxon_7_autocomplete) + assert taxon.matched_term == 'Vespidae' + + def test_taxon__conservation_status(): cs = Taxon.from_json(j_taxon_5_cs_status).conservation_status assert isinstance(cs, ConservationStatus) @@ -444,7 +454,6 @@ def test_taxon__conservation_status(): assert cs.status_name == 'imperiled' -# TODO: No sample data for this yet. Only on get_taxa_by_id response for particular taxa. def test_taxon__conservation_statuses(): css = Taxon.from_json(j_taxon_6_cs_statuses).conservation_statuses[0] assert isinstance(css, ConservationStatus)