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

feat: Force user agent in API #184

Merged
merged 3 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ First, instantiate an API object:
from openfoodfacts import API, APIVersion, Country, Environment, Flavor

api = API(
user_agent="<application name>",
username=None,
password=None,
country=Country.world,
Expand All @@ -21,7 +22,7 @@ api = API(
)
```

All parameters are optional, but here is a description of the parameters you can tweak:
All parameters are optional with the exception of user_agent, but here is a description of the parameters you can tweak:

- `username` and `password` are used to provide authentication (required for write requests)
- `country` is used to specify the country, which is used by the API to return product specific to the country or to infer which language to use by default. `world` (all products) is the default value
Expand Down
5 changes: 5 additions & 0 deletions openfoodfacts/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def send_get_request(
r = http_session.get(
url,
params=params,
headers={'User-Agent': api_config.user_agent},
timeout=api_config.timeout,
auth=get_http_auth(api_config.environment),
)
Expand All @@ -37,6 +38,7 @@ def send_for_urlencoded_post_request(
r = http_session.post(
url,
data=body,
headers={'User-Agent': api_config.user_agent},
timeout=api_config.timeout,
auth=get_http_auth(api_config.environment),
cookies=cookies,
Expand Down Expand Up @@ -202,6 +204,7 @@ def select_image(
r = http_session.post(
url,
data=params,
headers={'User-Agent': self.api_config.user_agent},
timeout=self.api_config.timeout,
auth=get_http_auth(self.api_config.environment),
cookies=cookies,
Expand All @@ -214,6 +217,7 @@ def select_image(
class API:
def __init__(
self,
user_agent: str,
username: Optional[str] = None,
password: Optional[str] = None,
country: Union[Country, str] = Country.world,
Expand Down Expand Up @@ -242,6 +246,7 @@ def __init__(
country = Country[country]

self.api_config = APIConfig(
user_agent=user_agent,
country=country,
flavor=Flavor[flavor],
version=APIVersion[version],
Expand Down
7 changes: 7 additions & 0 deletions openfoodfacts/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,7 @@ class Environment(str, enum.Enum):


class APIConfig(BaseModel):
user_agent: str
country: Country = Country.world
environment: Environment = Environment.org
flavor: Flavor = Flavor.off
Expand All @@ -839,6 +840,12 @@ def check_credentials(self):

return self

@model_validator(mode="after")
def check_user_agent(self):
if not isinstance(self.user_agent, str) or not self.user_agent.strip():
raise ValueError("User agent must be a string and cannot be empty.")
return self


class DatasetType(str, enum.Enum):
csv = "csv"
Expand Down
7 changes: 5 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

import openfoodfacts

TEST_USER_AGENT = "test_off_python"


class TestProducts(unittest.TestCase):

def test_get_product(self):
api = openfoodfacts.API(version="v2")
api = openfoodfacts.API(user_agent=TEST_USER_AGENT, version="v2")
code = "1223435"
response_data = {"product": {"code": "1223435"}}
with requests_mock.mock() as mock:
Expand All @@ -20,7 +23,7 @@ def test_get_product(self):
self.assertEqual(res, response_data)

def test_text_search(self):
api = openfoodfacts.API(version="v2")
api = openfoodfacts.API(user_agent=TEST_USER_AGENT, version="v2")
with requests_mock.mock() as mock:
response_data = {"products": ["kinder bueno"], "count": 1}
mock.get(
Expand Down
21 changes: 21 additions & 0 deletions tests/test_api_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import unittest

import pydantic_core

import openfoodfacts


class TestAPIConfig(unittest.TestCase):
def test_valid_user_agent(self):
config = openfoodfacts.APIConfig(user_agent="Valid User Agent")
assert config.user_agent == "Valid User Agent"

def test_invalid_user_agent_type(self):
with self.assertRaises(pydantic_core.ValidationError) as ctx:
openfoodfacts.APIConfig(user_agent=None)
self.assertTrue("valid string" in ctx.exception)

def test_blank_user_agent(self):
with self.assertRaises(pydantic_core.ValidationError) as ctx:
openfoodfacts.APIConfig(user_agent="")
self.assertTrue("cannot be empty" in ctx.exception)
Loading