Exemplo n.º 1
0
class ObservationField(BaseModel):
    """:fa:`tag` An observation field **definition**, based on the schema of
    `GET /observation_fields <https://www.inaturalist.org/pages/api+reference#get-observation_fields>`_.
    """

    allowed_values: List[str] = field(converter=safe_split, factory=list)
    created_at: datetime = datetime_now_field(doc='Date and time the observation field was created')
    datatype: str = field(default=None)  # Enum
    description: str = field(default=None)
    name: str = field(default=None)
    updated_at: datetime = datetime_now_field(
        doc='Date and time the observation field was last updated'
    )
    user_id: int = field(default=None)
    users_count: int = field(default=None)
    uuid: str = field(default=None)
    values_count: int = field(default=None)

    @property
    def _row(self) -> TableRow:
        return {
            'ID': self.id,
            'Type': self.datatype,
            'Name': self.name,
            'Description': self.description,
        }

    @property
    def _str_attrs(self) -> List[str]:
        return ['id', 'datatype', 'name', 'description']
Exemplo n.º 2
0
class Comment(BaseModel):
    """:fa:`comment` An observation comment, based on the schema of comments
    from `GET /observations <https://api.inaturalist.org/v1/docs/#!/Observations/get_observations>`_.
    """

    body: str = field(default='', doc='Comment text')
    created_at: datetime = datetime_now_field(
        doc='Date and time the comment was created')
    hidden: bool = field(default=None,
                         doc='Indicates if the comment is hidden')
    uuid: str = field(default=None, doc='Universally unique identifier')
    user: property = LazyProperty(User.from_json,
                                  type=User,
                                  doc='User that added the comment')

    # Unused attributes
    # created_at_details: Dict = field(factory=dict)
    # flags: List = field(factory=list)
    # moderator_actions: List = field(factory=list)

    @property
    def truncated_body(self):
        """Comment text, truncated"""
        truncated_body = self.body.replace('\n', ' ').strip()
        if len(truncated_body) > 50:
            truncated_body = truncated_body[:47].strip() + '...'
        return truncated_body

    @property
    def username(self) -> str:
        return self.user.login

    @property
    def _row(self) -> TableRow:
        return {
            'ID': self.id,
            'User': self.username,
            'Created at': self.created_at,
            'Comment': self.truncated_body,
        }

    @property
    def _str_attrs(self) -> List[str]:
        return ['id', 'username', 'created_at', 'truncated_body']
Exemplo n.º 3
0
class Project(BaseModel):
    """:fa:`users` An iNaturalist project, based on the schema of
    `GET /projects <https://api.inaturalist.org/v1/docs/#!/Projects/get_projects>`_.
    """

    banner_color: str = field(default=None)
    created_at: datetime = datetime_now_field(doc='Date and time the project was created')
    description: str = field(default=None, doc='Project description')
    header_image_url: str = field(default=None)
    hide_title: bool = field(default=None)
    icon: str = field(default=None, doc='URL for project icon')
    is_umbrella: bool = field(
        default=None,
        doc='Indicates if this is an umbrella project (containing observations from other projects)',
    )
    location: Coordinates = coordinate_pair()
    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
    admins: property = LazyProperty(
        ProjectUser.from_json_list, type=List[User], doc='Project admin users'
    )
    project_observation_fields: property = LazyProperty(
        ProjectObservationField.from_json_list,
        type=List[ProjectObservationField],
        doc='Observation fields used by the project',
    )
    user: property = LazyProperty(User.from_json, type=User, doc='User that created the project')

    # Unused attributes
    # flags: List = field(factory=list)
    # header_image_contain: bool = field(default=None)
    # header_image_file_name: str = field(default=None)
    # icon_file_name: str = field(default=None)
    # latitude: float = field(default=None)
    # longitude: float = field(default=None)
    # user_id: int = field(default=None)

    # Aliases
    @property
    def obs_fields(self):
        return self.project_observation_fields

    @property
    def obs_rules(self):
        return self.project_observation_rules

    @property
    def url(self) -> str:
        """Info URL on iNaturalist.org"""
        return f'{INAT_BASE_URL}/projects/{self.id}'

    @property
    def _row(self) -> TableRow:
        return {
            'ID': self.id,
            'Title': self.title,
            'Type': self.project_type,
            'URL': self.url,
        }

    @property
    def _str_attrs(self) -> List[str]:
        return ['id', 'title']
Exemplo n.º 4
0
class Observation(BaseModel):
    """:fa:`binoculars` An observation, based the schema of
    :v1:`GET /observations <Observations/get_observations>`
    """

    created_at: datetime = datetime_now_field(
        doc='Date and time the observation was created')
    captive: bool = field(
        default=None,
        doc='Indicates if the organism is non-wild (captive or cultivated)')
    community_taxon_id: int = field(
        default=None, doc='The current community identification taxon')
    description: str = field(default=None, doc='Observation description')
    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 with the community ID')
    identifications_most_disagree: bool = field(
        default=None,
        doc='Indicates if most identifications disagree with the community ID')
    identifications_some_agree: bool = field(
        default=None,
        doc='Indicates if some identifications agree with the community ID')
    license_code: str = field(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')
    num_identification_agreements: int = field(
        default=None,
        doc='Total identifications that agree with the community ID')
    num_identification_disagreements: int = field(
        default=None,
        doc='Total identifications that disagree with the community ID')
    oauth_application_id: str = field(
        default=None,
        doc='ID of the OAuth application used to create the observation, if any'
    )
    obscured: bool = field(
        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,
        doc='Place IDs associated with the observation coordinates')
    positional_accuracy: int = field(
        default=None,
        doc='GPS accuracy in meters (real accuracy, if obscured)')
    preferences: Dict[str, Any] = field(
        factory=dict,
        doc=
        'Any user observation preferences (related to community IDs, coordinate access, etc.)',
    )
    private_location: Coordinates = coordinate_pair(
        doc=
        ':fa:`lock` Private location in ``(latitude, logitude)`` decimal degrees'
    )
    private_place_ids: List[int] = field(
        factory=list,
        doc=
        ':fa:`lock` Place IDs associated with the private observation coordinates'
    )
    private_place_guess: str = field(
        default=None,
        doc=
        ':fa:`lock` Place name determined from private observation coordinates'
    )
    project_ids: List[int] = field(factory=list,
                                   doc='All associated project IDs')
    project_ids_with_curator_id: List[int] = field(
        factory=list,
        doc='Project IDs with a curator identification for this observation')
    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
    annotations: property = LazyProperty(Annotation.from_json_list,
                                         type=List[Annotation],
                                         doc='Observation annotations')
    comments: property = LazyProperty(Comment.from_json_list,
                                      type=List[Comment],
                                      doc='Observation comments')
    identifications: property = LazyProperty(Identification.from_json_list,
                                             type=List[Identification],
                                             doc='Observation identifications')
    ofvs: property = LazyProperty(
        ObservationFieldValue.from_json_list,
        type=List[ObservationFieldValue],
        doc='Observation field values',
    )
    photos: property = LazyProperty(Photo.from_json_list,
                                    type=List[Photo],
                                    doc='Observation photos')
    project_observations: property = LazyProperty(
        ProjectObservation.from_json_list,
        type=List[ProjectObservation],
        doc='Details on any projects that the observation has been added to',
    )
    taxon: property = LazyProperty(Taxon.from_json,
                                   type=Taxon,
                                   doc='Observation taxon')
    user: property = LazyProperty(User.from_json, type=User, doc='Observer')

    # Additional attributes from API response that aren't needed; just left here for reference
    # cached_votes_total: int = field(default=None)
    # comments_count: int = field(default=None)
    # created_at_details: Dict = field(factory=dict)
    # created_time_zone: str = field(default=None)
    # faves_count: int = field(default=None)
    # flags: List = field(factory=list)
    # geojson: Dict = field(factory=dict)
    # id_please: bool = field(default=None)
    # map_scale: int = field(default=None)
    # non_owner_ids: List = field(factory=list)
    # observed_on_details: Dict = field(factory=dict)
    # observed_on_string: str = field(default=None)
    # observation_photos: List[Photo] = field(converter=Photo.from_dict_list, factory=list)
    # observed_time_zone: str = field(default=None)
    # spam: bool = field(default=None)
    # time_observed_at: DateTime = datetime_attr
    # time_zone_offset: str = field(default=None)

    # Attributes that will only be used during init and then omitted
    temp_attrs = [
        'created_at_details',
        'observed_on_string',
        'observed_time_zone',
        'time_zone_offset',
    ]

    # Convert observation timestamps prior to __attrs_init__
    def __init__(
        self,
        # created_at_details: Dict = None,
        # observed_on_string: str = None,
        # observed_time_zone: str = None,
        # time_zone_offset: str = None,
        **kwargs,
    ):
        created_at_details = kwargs.pop('created_at_details', None)
        observed_on_string = kwargs.pop('observed_on_string', None)
        observed_time_zone = kwargs.pop('observed_time_zone', None)
        time_zone_offset = kwargs.pop('time_zone_offset', None)

        tz_offset = time_zone_offset
        tz_name = observed_time_zone
        created_date = (created_at_details or {}).get('date')

        if not isinstance(kwargs.get('created_at'), datetime) and created_date:
            kwargs['created_at'] = convert_observation_timestamp(
                created_date, tz_offset, tz_name)
        if not isinstance(kwargs.get('observed_on'),
                          datetime) and observed_on_string:
            kwargs['observed_on'] = convert_observation_timestamp(
                observed_on_string, tz_offset, tz_name, ignoretz=True)

        if not kwargs.get('uri'):
            kwargs[
                'uri'] = f'{INAT_BASE_URL}/observations/{kwargs.get("id", "")}'

        self.__attrs_init__(**kwargs)  # type: ignore

    @classmethod
    def from_id(cls, id: int):
        """Lookup and create a new Observation object from an ID"""
        from pyinaturalist.v1 import get_observation

        json = get_observation(id)
        return cls.from_json(json)

    @property
    def photo_url(self) -> Optional[str]:
        """Original size photo URL for first observation photo (if any)"""
        if not self.photos:
            return None
        return self.photos[0].original_url

    @property
    def thumbnail_url(self) -> Optional[str]:
        """Thumbnail size photo URL for first observation photo (if any)"""
        if not self.photos:
            return None
        return self.photos[0].thumbnail_url

    @property
    def _row(self) -> TableRow:
        return {
            'ID': self.id,
            'Taxon ID': self.taxon.id,
            'Taxon': self.taxon.full_name,
            'Observed on': self.observed_on,
            'User': self.user.login,
            'Location': self.place_guess or self.location,
        }

    @property
    def _str_attrs(self) -> List[str]:
        return ['id', 'taxon', 'observed_on', 'user', 'place_guess']
Exemplo n.º 5
0
class Identification(BaseModel):
    """:fa:`fingerprint,style=fas` An observation identification, based on the schema of
    `GET /identifications <https://api.inaturalist.org/v1/docs/#!/Identifications/get_identifications>`_.
    """

    body: str = field(default=None, doc='Comment text')
    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')
    current_taxon: bool = field(default=None)
    disagreement: bool = field(
        default=None,
        doc='Indicates if this identification disagrees with previous ones')
    hidden: bool = field(default=None)
    own_observation: bool = field(
        default=None, doc='Indicates if the indentifier is also the observer')
    previous_observation_taxon_id: int = field(
        default=None, doc='Previous observation taxon ID')
    taxon_change: bool = field(default=None)  # TODO: confirm type
    taxon_id: int = field(default=None, doc='Identification taxon ID')
    uuid: str = field(default=None, doc='Universally unique identifier')
    vision: bool = field(
        default=None,
        doc='Indicates if the taxon was selected from computer vision results')
    taxon: property = LazyProperty(Taxon.from_json,
                                   type=Taxon,
                                   doc='Identification taxon')
    user: property = LazyProperty(User.from_json,
                                  type=User,
                                  doc='User that added the indentification')

    @property
    def taxon_name(self) -> str:
        return self.taxon.full_name

    # Unused attributes
    # created_at_details: {}
    # spam: bool = field(default=None)
    # flags: List = field(factory=list)
    # moderator_actions: List = field(factory=list)
    # observation: {}

    @property
    def _row(self) -> TableRow:
        return {
            'ID': self.id,
            'Taxon ID': self.taxon.id,
            'Taxon': f'{self.taxon.emoji} {self.taxon.full_name}',
            'User': self.user.login,
            'Category': self.category.title(),
            'From CV': self.vision,
        }

    @property
    def _str_attrs(self) -> List[str]:
        return ['id', 'taxon_name', 'created_at']