Skip to content

Commit

Permalink
Fix group_id (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nacho Maiz authored Oct 17, 2023
1 parent 1232838 commit 1945b43
Show file tree
Hide file tree
Showing 32 changed files with 287 additions and 165 deletions.
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ pip install -e .[dev, doc, test, lint]

This will install all optional dependencies which are necessary for contributing to the code base.

## Fount API Key
## BAV API Key

You will need a Fount API key to perform requests to the API through `bavapi`.
You will need a BAV API key to perform requests to the API through `bavapi`.

To get and use an API key, see the [Authentication](https://wppbav.github.io/bavapi-sdk-python/latest/getting-started/authentication) section of the Getting Started guide.

In order to run integration tests, you will need to use an `.env` file to store your Fount API key as `FOUNT_API_KEY` (you can also set the environment variable directly). See the [instructions](https://wppbav.github.io/bavapi-sdk-python/latest/getting-started/authentication#recommended-way-to-manage-api-keys) for more details.
In order to run integration tests and the `bavapi-gen-refs` [command](https://wppbav.github.io/bavapi-sdk-python/latest/getting-started/reference-classes), you will need to use an `.env` file to store your BAV API key as `BAV_API_KEY` (you can also set the environment variable directly). See the [instructions](https://wppbav.github.io/bavapi-sdk-python/latest/getting-started/authentication#recommended-way-to-manage-api-keys) for more details.

## Tools and frameworks

Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ You will also need a BAV API token. For more information, go to the [Authenticat
### Dependencies

- `httpx >= 0.20`
- `nest-asyncio >= 1.5.6`
- `pandas >= 0.16.2`
- `nest-asyncio >= 1.5`
- `pandas >= 1.0`
- `pydantic >= 2.0`
- `tqdm >= 4.62`
- `typing-extensions >= 3.10` for Python < 3.10
- `typing-extensions >= 4.6` for Python < 3.12

## Installation

Expand Down Expand Up @@ -74,12 +74,20 @@ Once you have acquired a token, you can start using this library directly in pyt

## Features

- Support for all endpoints in the Fount API. Extended support for the `audiences`, `brands`, `brandscape-data` and `studies` endpoints.
- Support for all endpoints in the WPPBAV Fount API.
- Extended support for the following endpoints:
- `audiences`
- `brand-metrics`
- `brand-metric-groups`
- `brands`
- `brandscape-data`
- `categories`
- `collections`
- `sectors`
- `studies`
- Other endpoints are available via the `raw_query` functions and methods.
- Validates query parameters are of the correct types.
- Provides type hints for better IDE support.
- Retrieve multiple pages of data simultaneously.
- Monitors and prevents exceeding API rate limit.
- Validates query parameters are of the correct types and provides type hints for better IDE support.
- Retrieve multiple pages of data simultaneously, monitoring and preventing exceeding API rate limit.
- Both synchronous and asynchronous APIs for accessing BAV data.

## Documentation
Expand Down
6 changes: 3 additions & 3 deletions bavapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
>>> import bavapi
>>> res = bavapi.brands("TOKEN", name="Facebook")
For more advanced (and async compatibility), use the `Client` pattern:
For more advanced usage (and async compatibility), use the `bavapi.Client` class:
>>> import bavapi
>>> async with bavapi.Client("API_TOKEN") as bav:
Expand All @@ -31,14 +31,14 @@
from bavapi.query import Query
from bavapi.sync import (
audiences,
brand_metrics,
brand_metric_groups,
brand_metrics,
brands,
brandscape_data,
categories,
collections,
sectors,
raw_query,
sectors,
studies,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
These reference classes make it easier to filter results
by mapping commonly used IDs to human-readable options.
To generate reference classes you will need a Fount API token.
To generate reference classes you will need a WPPBAV Fount API token.
Please see the authentication section of the documentation for more info:
<https://developer.wppbav.com/docs/2.x/intro>
As well as the `bavapi` documentation on Authentication:
<https://wppbav.github.io/bavapi-sdk-python/getting-started/authentication>
"""
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def generate_source(
ref_name: str,
ref_items: Dict[str, str],
updated: datetime.datetime,
import_items: Tuple[str, str] = ("bavapi.reference._int_enum", "IntEnum"),
import_items: Tuple[str, str] = ("bavapi._reference.int_enum", "IntEnum"),
) -> str:
"""Generate updated module source from reference items.
Expand Down Expand Up @@ -225,7 +225,7 @@ def parse_args(argv: Optional[List[str]] = None) -> Args:
"Existing reference files will be overwritten.",
epilog="DON'T PUSH REFERENCES TO GIT! Add `bavapi_refs/` to `.gitignore`.",
)
parser.add_argument("-t", "--token", default="", help="Fount API token.")
parser.add_argument("-t", "--token", default="", help="WPPBAV Fount API token.")
parser.add_argument(
"-a", "--all", action="store_true", help="Generate all reference files."
)
Expand Down Expand Up @@ -266,10 +266,10 @@ def main(argv: Optional[List[str]] = None) -> int:
except ImportError as exc:
raise ValueError(
"You must specify a Fount API token with the `-t`/`--token` argument, "
"or install `python-dotenv` and set `FOUNT_API_KEY` in a `.env` file."
"or install `python-dotenv` and set `BAV_API_KEY` in a `.env` file."
) from exc

fount = Client(os.getenv("FOUNT_API_KEY", args.token))
fount = Client(os.getenv("BAV_API_KEY", args.token))

ref_configs: Dict[str, RefConfig] = {
"audiences": RefConfig("audiences", "audiences", parse_audiences),
Expand Down
File renamed without changes.
71 changes: 53 additions & 18 deletions bavapi/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
"""Fount API interface."""
"""
Asynchronous BAV API client interface.
Similar to `requests.Session` or `httpx.AsyncClient` (uses the latter as dependency).
Examples
--------
Create a client instance and make a request to the `brands` endpoint.
>>> async with bavapi.Client("TOKEN") as bav:
... result = await bav.brands("Facebook")
A more complex query:
>>> from bavapi_refs.audiences import Audiences
>>> async with bavapi.Client("TOKEN") as bav:
... bss = await bav.brandscape_data(
... country_code="UK",
... year_number=2022,
... audiences=Audiences.ALL_ADULTS,
... )
Multiple queries will share the client connection for better performance:
>>> async with bavapi.Client("TOKEN") as bav:
... result1 = await bav.brands("Facebook")
... result2 = await bav.brands("Instagram")
Use `Client.raw_query` (with `bavapi.Query`) for endpoints that aren't fully supported:
>>> query = bavapi.Query(filters=bavapi.filters.FountFilters(name="Meta"))
>>> async with bavapi.Client("TOKEN") as bav:
... result = await bav.raw_query("companies", params=query)
"""

# pylint: disable=too-many-arguments, too-many-lines

Expand All @@ -18,10 +52,11 @@
from bavapi.http import HTTPClient
from bavapi.parsing.responses import parse_response
from bavapi.query import Query
from bavapi.typing import JSONDict, OptionalListOr, Unpack, CommonQueryParams
from bavapi.typing import CommonQueryParams, JSONDict, OptionalListOr, Unpack

if TYPE_CHECKING:
from types import TracebackType

from pandas import DataFrame

__all__ = ("Client",)
Expand All @@ -37,7 +72,7 @@


class Client:
"""Asynchronous API to interact with the WPPBAV Fount.
"""Asynchronous API to interact with the WPPBAV Fount API.
This class uses `asyncio` to perform asynchronous requests to the Fount API.
Expand All @@ -48,7 +83,7 @@ class Client:
To use the Client class, you will need to precede calls with `await`:
```py
bav = Client("TOKEN") # creating instance does not use `await`
bav = Client("TOKEN") # creating client instance does not use `await`
data = await bav.brands("Swatch") # must use `await`
```
Expand All @@ -59,7 +94,7 @@ class Client:
Parameters
----------
auth_token : str, optional
Fount API authorization token, by default `''`
WPPBAV Fount API authorization token, by default `''`
per_page : int, optional
Default number of entries per page, by default 100
timeout : float, optional
Expand Down Expand Up @@ -197,8 +232,8 @@ async def aclose(self) -> None:
return await self._client.aclose()

async def raw_query(self, endpoint: str, params: Query[F]) -> List[JSONDict]:
"""Perform a raw GET query to the Fount API, returning the response JSON data
instead of a `pandas` DataFrame.
"""Perform a raw GET query to the WPPBAV Fount API, returning
the response JSON data instead of a `pandas` DataFrame.
Parameters
----------
Expand Down Expand Up @@ -240,13 +275,13 @@ async def audiences(
Fount audience ID, by default None
If an audience ID is provided, only that audience will be returned
active : Literal[0, 1]
active : Literal[0, 1], optional
Return active audiences only if set to `1`, by default 0
inactive : Literal[0, 1]
inactive : Literal[0, 1], optional
Return inactive audiences only if set to `1`, by default 0
public : Literal[0, 1]
public : Literal[0, 1], optional
Return active audiences only if set to `1`, by default 0
private : Literal[0, 1]
private : Literal[0, 1], optional
Return inactive audiences only if set to `1`, by default 0
groups : int or list[int], optional
Audience group ID or list of audience group IDs, by default None
Expand Down Expand Up @@ -336,13 +371,13 @@ async def brand_metrics(
Fount metric ID, by default None
If an metric ID is provided, only that metric will be returned
active : Literal[0, 1]
active : Literal[0, 1], optional
Return active brand metrics only if set to `1`, by default 0
inactive : Literal[0, 1]
inactive : Literal[0, 1], optional
Return inactive brand metrics only if set to `1`, by default 0
public : Literal[0, 1]
public : Literal[0, 1], optional
Return active brand metrics only if set to `1`, by default 0
private : Literal[0, 1]
private : Literal[0, 1], optional
Return inactive brand metrics only if set to `1`, by default 0
groups : int or list[int], optional
Brand metrics group ID or list of Brand metrics group IDs, by default None
Expand Down Expand Up @@ -429,9 +464,9 @@ async def brand_metric_groups(
Fount metric group ID, by default None
If an metric group ID is provided, only that metric group will be returned
active : Literal[0, 1]
active : Literal[0, 1], optional
Return active brand metric groups only if set to `1`, by default 0
inactive : Literal[0, 1]
inactive : Literal[0, 1], optional
Return inactive brand metric groups only if set to `1`, by default 0
filters : BrandMetricGroupsFilters or dict of filters, optional
BrandMetricGroupsFilters object or dictionary of filter parameters, by default None
Expand Down Expand Up @@ -739,7 +774,7 @@ async def categories(
If an category ID is provided, only that category will be returned
sector : int or list[int], optional
Filter categories by sector ID, by default 0
Filter categories by sector ID, by default None
filters : CategoriesFilters or dict of filters, optional
CategoriesFilters object or dictionary of filter parameters, by default None
fields : str or list[str], optional
Expand Down
2 changes: 1 addition & 1 deletion bavapi/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Exceptions for handling errors with the Fount API."""
"""Exceptions for handling errors with the WPPBAV Fount API."""


class APIError(Exception):
Expand Down
34 changes: 29 additions & 5 deletions bavapi/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
"""Filter objects for Fount API queries based on `pydantic`."""
"""
Filter objects for WPPBAV Fount API queries based on `pydantic`.
All endpoint filters are subclasses of `FountFilters`.
You can use any endpoint filter class with `raw_query` functions and methods,
but you must use endpoint-specific filters for each endpoint function or method.
Examples
--------
Use `BrandsFilters` with the `brands` endpoint:
>>> import bavapi
>>> bavapi.brands("TOKEN", filters=bavapi.filters.BrandsFilters(name="Facebook"))
`FountFilters` is compatible with all endpoints (including `raw_query`):
>>> bavapi.brands("TOKEN", filters=bavapi.filters.FountFilters(name="Facebook"))
Using the wrong filter can lead to unexpected results:
>>> bavapi.brands("TOKEN", filters=bavapi.filters.CategoriesFilters(country_codes="UK"))
The above example may work, but it is highly discouraged.
"""

# pylint: disable=no-name-in-module, too-few-public-methods

Expand Down Expand Up @@ -64,21 +88,21 @@ def ensure(
filters: Optional[FiltersOrMapping["FountFilters"]],
**addl_filters: InputSequenceOrValues,
) -> Optional[F]:
"""Ensure FountFilters class from dictionary or other FountFilters class.
"""Ensure `FountFilters` class from dictionary or other `FountFilters` class.
Defaults to values passed to `filters` when any additional filters overlap.
Parameters
----------
filters : FountFilters or dict of filter values, optional
Dictionary of filters or FountFilters class.
Dictionary of filters or `FountFilters` class.
**addl_filters : SequenceOrValues, optional
Additional filters to add to the new FountFilters instance.
Additional filters to add to the new `FountFilters` instance.
Returns
-------
FountFilters, optional
FountFilters class or None if `filters` is None and no additional filters are passed.
`FountFilters` class or `None` if `filters` is `None` and no additional filters are passed.
"""
addl_filters = {k: v for k, v in addl_filters.items() if v}

Expand Down
17 changes: 7 additions & 10 deletions bavapi/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
__all__ = ("HTTPClient",)


class Query(Protocol):
class _Query(Protocol):
"""Protocol for Query objects."""

item_id: Optional[int]
Expand All @@ -43,7 +43,7 @@ def to_params(self, endpoint: str) -> BaseParamsMapping:
"""HTTP-compatible params dictionary"""
raise NotImplementedError

def paginated(self, per_page: int, n_pages: int) -> Iterator["Query"]:
def paginated(self, per_page: int, n_pages: int) -> Iterator["_Query"]:
"""Yields Query objects with page parameters for paginated queries"""
raise NotImplementedError

Expand Down Expand Up @@ -138,7 +138,7 @@ async def aclose(self) -> None:
"""Asynchronously close all client connections."""
return await self.client.aclose()

async def get(self, endpoint: str, params: Query) -> httpx.Response:
async def get(self, endpoint: str, params: _Query) -> httpx.Response:
"""Perform GET request on the given endpoint.
Parameters
Expand Down Expand Up @@ -177,7 +177,7 @@ async def get(self, endpoint: str, params: Query) -> httpx.Response:
return resp

async def get_pages(
self, endpoint: str, params: Query, n_pages: int
self, endpoint: str, params: _Query, n_pages: int
) -> List[httpx.Response]:
"""Perform GET requests for a given number of pages on an endpoint.
Expand All @@ -200,19 +200,16 @@ async def get_pages(
for p in params.paginated(self.per_page, n_pages)
]
try:
return cast(
List[httpx.Response],
await tqdm.gather(
*tasks, desc=f"{endpoint} query", disable=not self.verbose
),
return await tqdm.gather(
*tasks, desc=f"{endpoint} query", disable=not self.verbose
)
except Exception as exc:
for task in tasks:
task.cancel()

raise exc

async def query(self, endpoint: str, params: Query) -> Iterator[JSONDict]:
async def query(self, endpoint: str, params: _Query) -> Iterator[JSONDict]:
"""Perform a paginated GET request on the given endpoint.
Parameters
Expand Down
Loading

0 comments on commit 1945b43

Please sign in to comment.