Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add latest resource #28

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
!!! warning

The OpenAQ python client is still under active development and may be unstable until
a v1.0.0 release.
a v1.0.0 release.


The OpenAQ Python SDK provides a Python interface for interacting the with OpenAQ API. The library is compatible with Python versions 3.8, 3.9, 3.10, 3.11 and 3.12
The OpenAQ Python SDK provides a Python interface for interacting the with
OpenAQ API. The library is compatible with Python versions 3.8, 3.9, 3.10, 3.11
and 3.12

Features:

* Synchronous and Asynchronous client options.
* Deserialized response classes.
* Fully type annotated.

- Synchronous and Asynchronous client options.
- Deserialized response classes.
- Comprehensive Type Annotations.
48 changes: 46 additions & 2 deletions openaq/_async/models/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import List, Tuple, Union

from openaq.shared.models import build_query_params
from openaq.shared.responses import LocationsResponse
from openaq.shared.responses import LatestResponse, LocationsResponse, SensorsResponse

from .base import AsyncResourceBase

Expand Down Expand Up @@ -33,10 +33,32 @@ async def get(self, locations_id: int) -> LocationsResponse:
location = await self._client._get(f"/locations/{locations_id}")
return LocationsResponse.read_response(location)

async def latest(self, locations_id: int) -> LatestResponse:
"""Retrieve latest measurements from a location.

Args:
locations_id: The locations ID of the location to retrieve.

Returns:
LatestResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
latest = await self._client._get(f"/locations/{locations_id}/latest")
return LatestResponse.read_response(latest)

async def list(
self,
page: int = 1,
limit: int = 1000,
limit: int = 100,
radius: Union[int, None] = None,
coordinates: Union[Tuple[float, float], None] = None,
bbox: Union[Tuple[float, float, float, float], None] = None,
Expand Down Expand Up @@ -117,3 +139,25 @@ async def list(

locations = await self._client._get("/locations", params=params)
return LocationsResponse.read_response(locations)

async def sensors(self, locations_id: int) -> SensorsResponse:
"""Retrieve sensors from a location.

Args:
locations_id: The locations ID of the location to retrieve.

Returns:
SensorsResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
sensors = await self._client._get(f"/locations/{locations_id}/sensors")
return SensorsResponse.read_response(sensors)
26 changes: 25 additions & 1 deletion openaq/_async/models/manufacturers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from openaq.shared.models import build_query_params
from openaq.shared.responses import ManufacturersResponse
from openaq.shared.responses import InstrumentsResponse, ManufacturersResponse

from .base import AsyncResourceBase

Expand Down Expand Up @@ -70,3 +70,27 @@ async def list(

manufacturers = await self._client._get("/manufacturers", params=params)
return ManufacturersResponse.read_response(manufacturers)

async def instruments(self, manufacturers_id: int) -> InstrumentsResponse:
"""Retrieve instruments of a manufacturer by ID.

Args:
manufacturers_id: The manufacturers ID of the manufacturer to retrieve.

Returns:
InstrumentsResponse: An instance representing the retrieved instruments.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
instruments_response = await self._client._get(
f"/manufacturers/{manufacturers_id}/instruments"
)
return InstrumentsResponse.read_response(instruments_response)
24 changes: 23 additions & 1 deletion openaq/_async/models/parameters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from openaq.shared.models import build_query_params
from openaq.shared.responses import ParametersResponse
from openaq.shared.responses import LatestResponse, ParametersResponse

from .base import AsyncResourceBase

Expand Down Expand Up @@ -97,3 +97,25 @@ async def list(

parameters = await self._client._get("/parameters", params=params)
return ParametersResponse.read_response(parameters)

async def latest(self, parameters_id: int) -> LatestResponse:
"""Retrieve latest measurements from a location.

Args:
parameters_id: The locations ID of the location to retrieve.

Returns:
LatestResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
latest = await self._client._get(f"/parameters/{parameters_id}/latest")
return LatestResponse.read_response(latest)
2 changes: 1 addition & 1 deletion openaq/_sync/models/licenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class Licenses(SyncResourceBase):
"""This provides methods to retrieve air monitor locations resource from the OpenAQ API."""
"""This provides methods to retrieve air monitor latest resource from the OpenAQ API."""

def get(self, licenses_id: int) -> LicensesResponse:
"""Retrieve a specific license by its licenses ID.
Expand Down
48 changes: 46 additions & 2 deletions openaq/_sync/models/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import List, Tuple, Union

from openaq.shared.models import build_query_params
from openaq.shared.responses import LocationsResponse
from openaq.shared.responses import LatestResponse, LocationsResponse, SensorsResponse

from .base import SyncResourceBase

Expand Down Expand Up @@ -33,10 +33,32 @@ def get(self, locations_id: int) -> LocationsResponse:
location_response = self._client._get(f"/locations/{locations_id}")
return LocationsResponse.read_response(location_response)

def latest(self, locations_id: int) -> LatestResponse:
"""Retrieve latest measurements from a location.

Args:
locations_id: The locations ID of the location to retrieve.

Returns:
LatestResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
latest = self._client._get(f"/locations/{locations_id}/latest")
return LatestResponse.read_response(latest)

def list(
self,
page: int = 1,
limit: int = 1000,
limit: int = 100,
radius: Union[int, None] = None,
coordinates: Union[Tuple[float, float], None] = None,
bbox: Union[Tuple[float, float, float, float], None] = None,
Expand Down Expand Up @@ -117,3 +139,25 @@ def list(

locations_response = self._client._get("/locations", params=params)
return LocationsResponse.read_response(locations_response)

def sensors(self, locations_id: int) -> SensorsResponse:
"""Retrieve sensors from a location.

Args:
locations_id: The locations ID of the location to retrieve.

Returns:
SensorsResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
sensors = self._client._get(f"/locations/{locations_id}/sensors")
return SensorsResponse.read_response(sensors)
26 changes: 25 additions & 1 deletion openaq/_sync/models/manufacturers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from openaq.shared.models import build_query_params
from openaq.shared.responses import ManufacturersResponse
from openaq.shared.responses import InstrumentsResponse, ManufacturersResponse

from .base import SyncResourceBase

Expand Down Expand Up @@ -70,3 +70,27 @@ def list(

manufacturer_response = self._client._get("/manufacturers", params=params)
return ManufacturersResponse.read_response(manufacturer_response)

def instruments(self, manufacturers_id: int) -> InstrumentsResponse:
"""Retrieve instruments of a manufacturer by ID.

Args:
manufacturers_id: The manufacturers ID of the manufacturer to retrieve.

Returns:
InstrumentsResponse: An instance representing the retrieved instruments.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
instruments_response = self._client._get(
f"/manufacturers/{manufacturers_id}/instruments"
)
return InstrumentsResponse.read_response(instruments_response)
24 changes: 23 additions & 1 deletion openaq/_sync/models/parameters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from openaq.shared.models import build_query_params
from openaq.shared.responses import ParametersResponse
from openaq.shared.responses import LatestResponse, ParametersResponse

from .base import SyncResourceBase

Expand Down Expand Up @@ -29,6 +29,28 @@ def get(self, parameters_id: int) -> ParametersResponse:
parameter_response = self._client._get(f"/parameters/{parameters_id}")
return ParametersResponse.read_response(parameter_response)

def latest(self, parameters_id: int) -> LatestResponse:
"""Retrieve latest measurements from a location.

Args:
parameters_id: The locations ID of the location to retrieve.

Returns:
LatestResponse: An instance representing the retrieved latest results.

Raises:
BadRequestError: Raised for HTTP 400 error, indicating a client request error.
NotAuthorized: Raised for HTTP 401 error, indicating the client is not authorized.
Forbidden: Raised for HTTP 403 error, indicating the request is forbidden.
NotFoundError: Raised for HTTP 404 error, indicating a resource is not found.
ValidationError: Raised for HTTP 422 error, indicating invalid request parameters.
RateLimit: Raised for HTTP 429 error, indicating rate limit exceeded.
ServerError: Raised for HTTP 500 error, indicating an internal server error or unexpected server-side issue.
GatewayTimeoutError: Raised for HTTP 504 error, indicating a gateway timeout.
"""
latest = self._client._get(f"/parameters/{parameters_id}/latest")
return LatestResponse.read_response(latest)

def list(
self,
page: int = 1,
Expand Down
48 changes: 43 additions & 5 deletions openaq/shared/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ def __post_init__(self):


@dataclass
class Latest(_ResourceBase):
class LatestBase(_ResourceBase):
"""latest measurement.

Attributes:
Expand All @@ -896,11 +896,11 @@ class Latest(_ResourceBase):

@dataclass
class Sensor(_ResourceBase):
"""Detailed information about an owner in OpenAQ.
"""Detailed information about an sensor in OpenAQ.

Attributes:
id: unique identifier for owner
name: owner name
id: unique identifier for sensor
name: sensor name
"""

id: int
Expand All @@ -909,7 +909,7 @@ class Sensor(_ResourceBase):
datetime_first: Datetime
datetime_last: Datetime
coverage: Coverage
latest: Latest
latest: LatestBase
summary: Summary


Expand All @@ -930,3 +930,41 @@ def __post_init__(self):
self.meta = Meta.load(self.meta)
if isinstance(self.results, list):
self.results = [Sensor.load(x) for x in self.results]


@dataclass
class Latest(_ResourceBase):
"""Latest measurement.

Attributes:
datetime: datetime object
value: measured value
coordinates: coordinates object with latitude and longitude of measurement
sensors_id: unique identifier for sensor
locations_id: unique identifier for location
"""

datetime: Datetime
value: float
coordinates: Coordinates
sensors_id: int
locations_id: int


@dataclass
class LatestResponse(_ResponseBase):
"""Representation of the API response for latest resource.

Attributes:
meta: a metadata object containing information about the results.
results: a list of latest records.
"""

results: List[Latest]

def __post_init__(self):
"""Sets class attributes to correct type after checking input type."""
if isinstance(self.meta, dict):
self.meta = Meta.load(self.meta)
if isinstance(self.results, list):
self.results = [Latest.load(x) for x in self.results]
Loading
Loading