From 4a333066957861604fc6d6a69c00aa687c4743d4 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 | 3 --- tests/webservice_test.py | 2 -- 3 files changed, 8 deletions(-) diff --git a/tests/database_test.py b/tests/database_test.py index 2c831df..a5db7a3 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -1,11 +1,8 @@ import datetime import ipaddress -import sys import unittest from unittest.mock import MagicMock, patch -sys.path.append("..") - import maxminddb import geoip2.database diff --git a/tests/models_test.py b/tests/models_test.py index 7cce9a8..36d2997 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -1,10 +1,7 @@ import ipaddress -import sys import unittest from typing import ClassVar -sys.path.append("..") - import geoip2.models diff --git a/tests/webservice_test.py b/tests/webservice_test.py index f918753..61ec4ce 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -3,7 +3,6 @@ import asyncio import copy import ipaddress -import sys import unittest from abc import ABC, abstractmethod from collections import defaultdict @@ -13,7 +12,6 @@ import pytest_httpserver from pytest_httpserver import HeaderValueMatcher -sys.path.append("..") import geoip2 from geoip2.errors import ( AddressNotFoundError, From 7cd0e2bdb8b19d87b500ec16c4e1564361f68536 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 ++ pyproject.toml | 14 ++++++++++++-- src/geoip2/webservice.py | 39 +++++++++++++++++++++++++-------------- tests/webservice_test.py | 2 ++ 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index c4f3a2f..ef71dcd 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 install from PyPI, you may also use ``pip`` from the source directory: diff --git a/pyproject.toml b/pyproject.toml index 2e73112..32550de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,7 @@ authors = [ {name = "Gregory Oschwald", email = "goschwald@maxmind.com"}, ] dependencies = [ - "aiohttp>=3.6.2,<4.0.0", "maxminddb>=2.7.0,<3.0.0", - "requests>=2.24.0,<3.0.0", ] requires-python = ">=3.9" readme = "README.rst" @@ -30,6 +28,14 @@ classifiers = [ "Topic :: Internet :: Proxy Servers", ] +[project.optional-dependencies] +aiohttp = [ + "aiohttp>=3.6.2,<4.0.0", +] +requests = [ + "requests>=2.24.0,<3.0.0", +] + [dependency-groups] dev = [ "pytest>=8.3.5", @@ -115,6 +121,10 @@ commands = [ [tool.tox.env.lint] description = "Code linting" python = "3.13" +extras = [ + "aiohttp", + "requests", +] dependency_groups = [ "dev", "lint", diff --git a/src/geoip2/webservice.py b/src/geoip2/webservice.py index 0316116..9810898 100644 --- a/src/geoip2/webservice.py +++ b/src/geoip2/webservice.py @@ -27,10 +27,18 @@ import json from typing import TYPE_CHECKING, cast -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 @@ -52,14 +60,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: """Base class for AsyncClient and Client.""" @@ -352,10 +352,18 @@ async def insights(self, ip_address: IPAddress = "me") -> Insights: ) async def _session(self) -> aiohttp.ClientSession: + if aiohttp is None: + msg = "aiohttp is required for async mode; install `GeoIP2[aiohttp]`" + raise ImportError(msg) + 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), ) @@ -468,7 +476,10 @@ def __init__( # noqa: PLR0913 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/tests/webservice_test.py b/tests/webservice_test.py index 61ec4ce..1f5fd1d 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -400,6 +400,7 @@ class TestClient(TestBaseClient): client: Client def setUp(self) -> None: + pytest.importorskip("requests") self.client_class = Client self.client = Client(42, "abcdef123456") self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") # noqa: SLF001 @@ -413,6 +414,7 @@ class TestAsyncClient(TestBaseClient): client: AsyncClient def setUp(self) -> None: + pytest.importorskip("aiohttp") self._loop = asyncio.new_event_loop() self.client_class = AsyncClient self.client = AsyncClient(42, "abcdef123456")