Skip to content

Commit

Permalink
More useful and less dogmatic docstrings, small fixes, use httpx rath…
Browse files Browse the repository at this point in the history
…er than requests.
  • Loading branch information
Schalk1e committed May 15, 2024
1 parent 4b742a3 commit fb32cd9
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 120 deletions.
105 changes: 104 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ isort = "^5.13.2"
yamllint = "^1.35.1"
ruff = "^0.4.4"
attrs = "^23.2.0"
httpx = "^0.27.0"


[tool.poetry.group.types.dependencies]
Expand Down
11 changes: 2 additions & 9 deletions src/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@


class MissingConfig(Exception):
"""Exception to return if an env var is not found in the global
environment.
"""
"""Raised if a required config environment variable is not set."""


def config_from_env(key: str) -> str:
"""Checks whether key exists in global environment and returns it if it
does. Else it returns a MissingConfig Exception.
Args:
key: The name of the env var to return. (str)
does. Else it raises a MissingConfig Exception.
Returns:
str
"""
if not (value := os.environ.get(key, None)):
raise MissingConfig(f"{key} not set in the global environment")
Expand Down
60 changes: 13 additions & 47 deletions src/api/aaq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,24 @@
from urllib.parse import urljoin

from attrs import define
from requests import Response, Session
from httpx import Client

from .. import config_from_env

API_KEY = config_from_env("AAQ_API_KEY")
BASE_URL = config_from_env("AAQ_API_BASE_URL")


@define
class BaseSession(Session):
"""Base requests session to use in requests to different API endpoints in
the module.
class BaseClient(Client):
"""To be used in various enpoint specific requests.
Args:
url_base: The base url to reference (str) (This has to be str | bytes in
accordance with what requests accepts?)
*args
**kwargs
"""
Need to subclass here in order to overload `paginate_get` which is useful
for each of the different endpoint specific request in this submodule.
url_base: str
"""

# TODO: attrs doesn't like subclassing.
def __attrs_post_init__(self, *args, **kwargs) -> None:
def __init__(self, *args, **kwargs) -> Client:
super().__init__(*args, **kwargs)

def request(self, method: str, url: str, **kwargs) -> Response:
"""Send request to an API endpoint by providing only the endpoint with
the base url as part of initialisation.
Args:
method: The method type. aGET, PUT etc. (str)
url: The endpoint. (str)
**kwargs
Returns:
requests.Response
"""
url = urljoin(self.url_base, url)
return super().request(method, url, **kwargs)

def get(self, url: str, limit: int = 100, **kwargs) -> list[dict]:
"""Paginate over pages in an AAQ endpoint up to a limit.
Args:
url: the endpoint. (str)
limit: The limit of pages to paginate over. (int)
Returns:
list[Response]
"""
def paginate_get(self, url: str, limit: int = 100, **kwargs) -> list[dict]:
"""Paginate over pages in an AAQ endpoint up to a limit."""
params = {**kwargs}

params["offset"] = 0
Expand All @@ -68,7 +35,7 @@ def get(self, url: str, limit: int = 100, **kwargs) -> list[dict]:
params["offset"] + params["limit"],
sep=" ",
)
response = self.request("GET", url, params=params)
response = self.get(url, params=params)
response.raise_for_status()
result = response.json()["result"]
response_list.append(result)
Expand All @@ -82,13 +49,12 @@ def get(self, url: str, limit: int = 100, **kwargs) -> list[dict]:
return response_list


base_session = BaseSession(url_base=BASE_URL)
base_session.params = {}
base_session.headers = {}
base_session.headers = {
headers = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/vnd.v1+json",
"Content-Type": "application/json",
}

base_client = BaseClient(base_url=BASE_URL, headers=headers)

from .main import pyAAQ as pyAAQ
10 changes: 5 additions & 5 deletions src/api/aaq/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from api.aaq.requests.inbounds_ud import InboundsUD
from api.aaq.requests.urgency_rules import UrgencyRules

from . import base_session
from . import base_client


class pyAAQ:
"""A wrapper class for the various AAQ endpoints."""

inbounds = Inbounds(base_session)
faqmatches = FAQMatches(base_session)
inbounds_ud = InboundsUD(base_session)
urgency_rules = UrgencyRules(base_session)
inbounds = Inbounds(base_client)
faqmatches = FAQMatches(base_client)
inbounds_ud = InboundsUD(base_client)
urgency_rules = UrgencyRules(base_client)
20 changes: 8 additions & 12 deletions src/api/aaq/requests/faqmatches.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
from attrs import define
from pandas import DataFrame, concat

from .. import BaseSession
from .. import BaseClient


@define
class FAQMatches:
"""Dedicated to the faqmaches endpoint of the AAQ Data Export API.
"""Dedicated to the faqmatches endpoint of the AAQ Data Export API."""

Args:
A BaseSession object.
"""

base_session: type[BaseSession]
base_client: type[BaseClient]

def get_faqmatches(self, **kwargs) -> DataFrame:
"""Get a pandas DataFrame of faqmatches.
Args:
**kwargs
No time-based query parameters are supported for this endpoint.
Should return the full faqmatches object or an empty DataFrame if
no records are returned by the API.
Returns:
pandas.DataFrame
"""

url = "faqmatches"

response_list = self.base_session.get(url, **kwargs)
response_list = self.base_client.paginate_get(url, **kwargs)

response_list = [
{key: str(d[key]) for key in d} for d in response_list
Expand Down
40 changes: 20 additions & 20 deletions src/api/aaq/requests/inbounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,36 @@
from attrs import define
from pandas import DataFrame, concat

from .. import BaseSession
from .. import BaseClient


@define
class Inbounds:
"""Dedicated to the inbounds endpoint of the AAQ Data Export API.
This allows us to retrieve inbound messages sent to the IDI AAQ instance
for a given project.
This allows us to retrieve inbound messages sent to the IDI AAQ instance,
the ranks of the various FAQ's as determined by the model for each inbound
etc.
Args:
A BaseSession object.
"""

base_session: type[BaseSession]
base_client: type[BaseClient]

def get_inbounds(self, **kwargs) -> DataFrame:
"""Get a pandas DataFrame of inbound messages.
Args:
**kwargs
start_datetime: [via *kwargs] The start datetime query parameter to
send. Example: '2020-01-01 00:00:00'
end_datetime: [via **kwargs] The end datetime query parameter to
send. Example: '2020-01-01 00:00:00'
This endpoint supports time-based query parameters which can
be passed to this method as kwargs as in the following example:
pyAAQ.inbounds.get_inbounds(
start_datetime="2020-01-01 00:00:00",
end_datetime="2020-12-31 00:00:00"
)
Returns:
pandas.DataFrame
"""
url = "inbounds"

response_list = self.base_session.get(url, **kwargs)
response_list = self.base_client.paginate_get(url, **kwargs)

response_list = [
{key: str(d[key]) for key in d} for d in response_list
Expand All @@ -54,15 +51,18 @@ def get_inbounds(self, **kwargs) -> DataFrame:
def get_faqranks(self, **kwargs) -> DataFrame:
"""Get a pandas DataFrame of faqranks for each inbound message.
Args:
**kwargs
This endpoint supports time-based query parameters which can
be passed to this method as kwargs as in the following example:
pyAAQ.inbounds.get_faqranks(
start_datetime="2020-01-01 00:00:00",
end_datetime="2020-12-31 00:00:00"
)
Returns:
pandas.DataFrame
"""
url = "inbounds"

response_list = self.base_session.get(url, **kwargs)
response_list = self.base_client.paginate_get(url, **kwargs)

response_list = [
{key: str(d[key]) for key in d} for d in response_list
Expand Down
Loading

0 comments on commit fb32cd9

Please sign in to comment.