From 9a24ea1ad53235b9633ee398f2092fe37ea58b96 Mon Sep 17 00:00:00 2001 From: Jordan Cook Date: Sun, 4 Jul 2021 12:45:23 -0500 Subject: [PATCH] Just list attr options in docs instead of using validators --- pyinaturalist/api_docs/model_docs.py | 8 +++++--- pyinaturalist/models/__init__.py | 5 +++-- pyinaturalist/models/identification.py | 3 +-- pyinaturalist/models/observation.py | 12 +++--------- pyinaturalist/models/photo.py | 8 ++------ pyinaturalist/models/search.py | 4 ++-- pyinaturalist/models/taxon.py | 7 +++---- 7 files changed, 19 insertions(+), 28 deletions(-) diff --git a/pyinaturalist/api_docs/model_docs.py b/pyinaturalist/api_docs/model_docs.py index de2fbb8b..e10ba5ea 100644 --- a/pyinaturalist/api_docs/model_docs.py +++ b/pyinaturalist/api_docs/model_docs.py @@ -44,7 +44,8 @@ def get_model_classes() -> List[Type]: # 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 +71,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/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..9eeb0353 100644 --- a/pyinaturalist/models/observation.py +++ b/pyinaturalist/models/observation.py @@ -30,7 +30,6 @@ datetime_now_field, define_model_collection, field, - is_in, upper, ) from pyinaturalist.v1 import get_observation @@ -48,9 +47,7 @@ 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' - ) + 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 +55,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') @@ -87,7 +81,7 @@ class Observation(BaseModel): 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') + quality_grade: str = field(default=None, options=QUALITY_GRADES, doc='Quality grade') site_id: int = field( default=None, doc='Site ID for iNaturalist network members, or ``1`` for inaturalist.org' ) 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/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 1eff834b..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' @@ -143,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(