From e0873761d5061729b3e05e6686e93fc0d915c324 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 27 Aug 2024 10:25:52 +0300 Subject: [PATCH 1/2] Do not manipulate `sys.path` in tests --- tests/database_test.py | 3 --- tests/models_test.py | 2 -- tests/webservice_test.py | 3 --- 3 files changed, 8 deletions(-) diff --git a/tests/database_test.py b/tests/database_test.py index 0974b74..8ec22ce 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -4,11 +4,8 @@ from __future__ import unicode_literals import ipaddress -import sys import unittest -sys.path.append("..") - import geoip2.database import geoip2.errors import maxminddb diff --git a/tests/models_test.py b/tests/models_test.py index 9f4fa72..e298229 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -7,8 +7,6 @@ from typing import Dict import unittest -sys.path.append("..") - import geoip2.models diff --git a/tests/webservice_test.py b/tests/webservice_test.py index 813738f..7318d7d 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -4,8 +4,6 @@ import asyncio import copy import ipaddress -import json -import sys from typing import cast, Dict import unittest from pytest_httpserver import HeaderValueMatcher @@ -14,7 +12,6 @@ from collections import defaultdict -sys.path.append("..") import geoip2 from geoip2.errors import ( AddressNotFoundError, From ff4972eefdc123f9bdab3a29c3a7e9c1c13b1441 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 27 Aug 2024 10:27:12 +0300 Subject: [PATCH 2/2] Make aiohttp and requests optional dependencies Closes #104 (supersedes it) Fixes #101 --- README.rst | 2 ++ geoip2/webservice.py | 42 ++++++++++++++++++++++++++-------------- pyproject.toml | 8 ++++++-- setup.cfg | 11 +++++++++-- tests/webservice_test.py | 2 ++ 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index e1e8913..35fa318 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,8 @@ To install the ``geoip2`` module, type: .. code-block:: bash $ pip install geoip2 + $ pip install geoip2[aiohttp] # Install aiohttp as well + $ pip install geoip2[requests] # Install requests as well If you are not able to use pip, you may also use easy_install from the source directory: diff --git a/geoip2/webservice.py b/geoip2/webservice.py index 2c5eb81..e0ae115 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -25,14 +25,24 @@ """ +from __future__ import annotations + import ipaddress import json from typing import Any, Dict, cast, List, Optional, Type, Union -import aiohttp -import aiohttp.http -import requests -import requests.utils +try: + import aiohttp + import aiohttp.http +except ImportError: + aiohttp = None # type: ignore[assignment] + +try: + import requests + import requests.utils +except ImportError: + requests = None # type: ignore[assignment] + import geoip2 import geoip2.models @@ -48,14 +58,6 @@ from geoip2.models import City, Country, Insights from geoip2.types import IPAddress -_AIOHTTP_UA = ( - f"GeoIP2-Python-Client/{geoip2.__version__} {aiohttp.http.SERVER_SOFTWARE}" -) - -_REQUEST_UA = ( - f"GeoIP2-Python-Client/{geoip2.__version__} {requests.utils.default_user_agent()}" -) - class BaseClient: # pylint: disable=missing-class-docstring, too-few-public-methods _account_id: str @@ -326,10 +328,19 @@ async def insights(self, ip_address: IPAddress = "me") -> Insights: ) async def _session(self) -> aiohttp.ClientSession: + if aiohttp is None: + raise ImportError( + "aiohttp is required for async mode; install `GeoIP2[aiohttp]`" + ) + if not hasattr(self, "_existing_session"): + user_agent = ( + f"GeoIP2-Python-Client/{geoip2.__version__} " + f"{aiohttp.http.SERVER_SOFTWARE}" + ) self._existing_session = aiohttp.ClientSession( auth=aiohttp.BasicAuth(self._account_id, self._license_key), - headers={"Accept": "application/json", "User-Agent": _AIOHTTP_UA}, + headers={"Accept": "application/json", "User-Agent": user_agent}, timeout=aiohttp.ClientTimeout(total=self._timeout), ) @@ -436,7 +447,10 @@ def __init__( # pylint: disable=too-many-arguments self._session = requests.Session() self._session.auth = (self._account_id, self._license_key) self._session.headers["Accept"] = "application/json" - self._session.headers["User-Agent"] = _REQUEST_UA + self._session.headers["User-Agent"] = ( + f"GeoIP2-Python-Client/{geoip2.__version__}" + f" {requests.utils.default_user_agent()}" + ) if proxy is None: self._proxies = None else: diff --git a/pyproject.toml b/pyproject.toml index 5befe70..8fd1bd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,7 @@ authors = [ {name = "Gregory Oschwald", email = "goschwald@maxmind.com"}, ] dependencies = [ - "aiohttp>=3.6.2,<4.0.0", "maxminddb>=2.5.1,<3.0.0", - "requests>=2.24.0,<3.0.0", ] requires-python = ">=3.8" readme = "README.rst" @@ -38,6 +36,12 @@ classifiers = [ test = [ "pytest-httpserver>=1.0.10", ] +aiohttp = [ + "aiohttp>=3.6.2,<4.0.0", +] +requests = [ + "requests>=2.24.0,<3.0.0", +] [tool.setuptools.package-data] geoip2 = ["py.typed"] diff --git a/setup.cfg b/setup.cfg index fb01367..70c4ed0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,9 @@ geoip2 = py.typed disable = duplicate-code [tox:tox] -envlist = {py38,py39,py310,py311,py312}-test,py312-{black,lint,flake8,mypy} +envlist = + {py38,py39,py310,py311,py312}-test{,-all} + py312-{black,lint,flake8,mypy} [gh-actions] python = @@ -19,11 +21,13 @@ python = 3.11: py311 3.12: py312,black,lint,flake8,mypy -[testenv:{py38,py39,py310,py311,py312}-test] +[testenv:{py38,py39,py310,py311,py312}-test{,-all}] deps = pytest-httpserver pytest commands = pytest tests +extras = + all: aiohttp, requests [testenv:py312-black] deps = black @@ -45,3 +49,6 @@ deps = types-requests voluptuous-stubs commands = mypy geoip2 tests +extras = + aiohttp + requests diff --git a/tests/webservice_test.py b/tests/webservice_test.py index 7318d7d..77c99a7 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -355,6 +355,7 @@ def test_missing_constructor_args(self): class TestClient(TestBaseClient): def setUp(self): + pytest.importorskip("requests") self.client_class = Client self.client = Client(42, "abcdef123456") self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") @@ -365,6 +366,7 @@ def run_client(self, v): class TestAsyncClient(TestBaseClient): def setUp(self): + pytest.importorskip("aiohttp") self._loop = asyncio.new_event_loop() self.client_class = AsyncClient self.client = AsyncClient(42, "abcdef123456")