Skip to content

Commit

Permalink
Fix broken response_format parameter in v0.get_observations()
Browse files Browse the repository at this point in the history
  • Loading branch information
JWCook committed Jul 17, 2021
1 parent 4571760 commit cece2aa
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 37 deletions.
31 changes: 17 additions & 14 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# History

## 0.14 (2021-07-14)
## 0.14.1 (2021-07-TBD)
* Fix broken `response_format` parameter in `v0.get_observations()`

## 0.14.0 (2021-07-14)
[See all Issues & PRs for 0.14](https://github.com/niconoe/pyinaturalist/milestone/5?closed=1)

### New Endpoints
Expand Down Expand Up @@ -71,7 +74,7 @@ Model features:
* Set up pyinaturalist-notebook to be [runnable with Binder](https://mybinder.org/v2/gh/niconoe/pyinaturalist/main?filepath=examples)

-----
## 0.13 (2021-05-22)
## 0.13.0 (2021-05-22)
[See all Issues & PRs for 0.13](https://github.com/niconoe/pyinaturalist/milestone/4?closed=1)

### New Endpoints
Expand Down Expand Up @@ -120,7 +123,7 @@ Model features:
### 0.12.1 (2021-03-07)
* Add undocumented `ident_user_id` parameter to `get_observations()`

## 0.12 (2021-02-02)
## 0.12.0 (2021-02-02)
[See all Issues & PRs for 0.12](https://github.com/niconoe/pyinaturalist/milestone/3?closed=1)

### New Endpoints
Expand Down Expand Up @@ -159,7 +162,7 @@ Model features:
* Removed request parameters that were deprecated in 0.11

-----
## 0.11 (2020-11-04)
## 0.11.0 (2020-11-04)
[See all Issues & PRs for 0.11](https://github.com/niconoe/pyinaturalist/milestone/2?closed=1)

### New Endpoints
Expand Down Expand Up @@ -203,7 +206,7 @@ Model features:
* Added testing & support for python 3.9
* Added parameter validation for multiple-choice request parameters

## 0.10 (2020-06-16)
## 0.10.0 (2020-06-16)
[See all Issues & PRs for 0.10](https://github.com/niconoe/pyinaturalist/milestone/1?closed=1)

### New Endpoints
Expand All @@ -223,7 +226,7 @@ Model features:

* Bugfix: proper support for boolean and integer list parameters ([Issue #17](https://github.com/niconoe/pyinaturalist/issues/17))

## 0.9 (2020-05-06)
## 0.9.0 (2020-05-06)

### New Endpoints
* Added new functions for Node API **Taxa** endpoints:
Expand All @@ -232,38 +235,38 @@ Model features:
* `node_api.get_taxa_autocomplete()`
* `node_api.get_taxa_by_id()`

## 0.8 (2019-07-11)
## 0.8.0 (2019-07-11)

* All functions now take an optional `user-agent <https://en.wikipedia.org/wiki/User_agent>`_ parameter in order to identify yourself to iNaturalist. If not set, `Pyinaturalist/<VERSION>` will be used.

## 0.7 (2019-05-08)
## 0.7.0 (2019-05-08)

* `rest_api.delete_observation()` now raises `ObservationNotFound` if the observation doesn't exist
* minor dependencies update for security reasons

## 0.6 (2018-11-15)
## 0.6.0 (2018-11-15)

* New function: `rest_api.delete_observation()`

## 0.5 (2018-11-05)
## 0.5.0 (2018-11-05)

* New function: `node_api.get_observation()`

## 0.4 (2018-11-05)
## 0.4.0 (2018-11-05)

* `create_observation()` now raises exceptions in case of errors.

## 0.3 (2018-11-05)
## 0.3.0 (2018-11-05)

* `update_observation()` now raises exceptions in case of errors.

## 0.2 (2018-10-31)
## 0.2.0 (2018-10-31)

* Better infrastructure (type annotations, documentation, ...)
* Dropped support for Python 2.
* New function: `update_observation()`
* `rest_api.AuthenticationError` is now `exceptions.AuthenticationError`

## 0.1 (2018-10-10)
## 0.1.0 (2018-10-10)

* First release on PyPI.
2 changes: 1 addition & 1 deletion pyinaturalist/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# flake8: noqa: F401, F403
__version__ = '0.14.0'
__version__ = '0.14.1'
DEFAULT_USER_AGENT = f'pyinaturalist/{__version__}'
user_agent = DEFAULT_USER_AGENT

Expand Down
4 changes: 2 additions & 2 deletions pyinaturalist/docs/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def _observation_rest_only(
h1: AnyDate = None,
h2: AnyDate = None,
extra: str = None,
converters: str = 'json',
response_format: str = 'json',
):
"""
has: Catch-all for some boolean selectors. This can be used multiple times, e.g.
Expand All @@ -298,7 +298,7 @@ def _observation_rest_only(
**'fields'** returns observation field values,
**'observation_photos'** returns information about the photos' relationship with the
observation, like their order.
converters: A supported response format to return
response_format: A supported response format to return
"""


Expand Down
4 changes: 4 additions & 0 deletions pyinaturalist/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ def from_json(cls: Type[T], value: JsonResponse, **kwargs) -> 'BaseModel':
Omits any invalid fields and ``None`` values, so we use our default factories instead
(e.g. for empty dicts and lists).
"""
if isinstance(value, cls):
return value

attr_names = [a.lstrip('_') for a in fields_dict(cls)]
if cls.temp_attrs:
attr_names.extend(cls.temp_attrs)

valid_json = {k: v for k, v in value.items() if k in attr_names and v is not None}
return cls(**valid_json, **kwargs) # type: ignore

Expand Down
9 changes: 8 additions & 1 deletion pyinaturalist/models/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,16 @@ def from_id(cls, id: int):
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 URL for first observation photo (if any)"""
"""Thumbnail size photo URL for first observation photo (if any)"""
if not self.photos:
return None
return self.photos[0].thumbnail_url
Expand Down
10 changes: 5 additions & 5 deletions pyinaturalist/v0/observations.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,15 @@ def get_observations(**params) -> Union[List, str]:
Returns:
Return type will be ``dict`` for the ``json`` response format, and ``str`` for all others.
"""
converters = params.pop('converters', 'json')
if converters == 'geojson':
response_format = params.pop('response_format', 'json')
if response_format == 'geojson':
raise ValueError('For geojson format, use pyinaturalist.v1.get_geojson_observations')
if converters not in OBSERVATION_FORMATS:
if response_format not in OBSERVATION_FORMATS:
raise ValueError('Invalid response format')
validate_multiple_choice_param(params, 'order_by', REST_OBS_ORDER_BY_PROPERTIES)

response = get(f'{API_V0_BASE_URL}/observations.{converters}', **params)
if converters == 'json':
response = get(f'{API_V0_BASE_URL}/observations.{response_format}', **params)
if response_format == 'json':
observations = response.json()
observations = convert_all_coordinates(observations)
observations = convert_all_timestamps(observations)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyinaturalist"
version = "0.14.0"
version = "0.14.1"
description = "iNaturalist API client for python"
authors = ["Nicolas Noé <[email protected]>", "Jordan Cook <[email protected]>"]
license = "MIT"
Expand Down
26 changes: 13 additions & 13 deletions test/v0/test_observations_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,27 @@
from test.conftest import load_sample_data


def get_observations_response(converters):
converters = converters.replace('widget', 'js')
return load_sample_data(f'get_observations.{converters}')
def get_observations_response(response_format):
response_format = response_format.replace('widget', 'js')
return load_sample_data(f'get_observations.{response_format}')


@pytest.mark.parametrize('converters', OBSERVATION_FORMATS)
def test_get_observations(converters, requests_mock):
@pytest.mark.parametrize('response_format', OBSERVATION_FORMATS)
def test_get_observations(response_format, requests_mock):
"""Test all supported observation data formats"""
mock_response = get_observations_response(converters)
key = 'json' if converters == 'json' else 'text'
mock_response = get_observations_response(response_format)
key = 'json' if response_format == 'json' else 'text'

requests_mock.get(
f'{API_V0_BASE_URL}/observations.{converters}',
f'{API_V0_BASE_URL}/observations.{response_format}',
status_code=200,
**{key: mock_response},
)

observations = get_observations(taxon_id=493595, converters=converters)
observations = get_observations(taxon_id=493595, response_format=response_format)

# For JSON format, ensure type conversions were performed
if converters == 'json':
if response_format == 'json':
assert observations[0]['latitude'] == 50.646894
assert observations[0]['longitude'] == 4.360086
assert observations[0]['created_at'] == datetime(2018, 9, 5, 12, 31, 8, 48000, tzinfo=tzutc())
Expand All @@ -48,10 +48,10 @@ def test_get_observations(converters, requests_mock):
assert observations == mock_response


@pytest.mark.parametrize('converters', ['geojson', 'yaml'])
def test_get_observations__invalid_format(converters):
@pytest.mark.parametrize('response_format', ['geojson', 'yaml'])
def test_get_observations__invalid_format(response_format):
with pytest.raises(ValueError):
get_observations(taxon_id=493595, converters=converters)
get_observations(taxon_id=493595, response_format=response_format)


def test_create_observation(requests_mock):
Expand Down

0 comments on commit cece2aa

Please sign in to comment.