diff --git a/README.rst b/README.rst index a20e771..86c325a 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 cd2aac4..ed5c59e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,7 @@ authors = [ {name = "Gregory Oschwald", email = "goschwald@maxmind.com"}, ] dependencies = [ - "aiohttp>=3.14.1,<4.0.0", "maxminddb>=3.0.0,<4.0.0", - "requests>=2.24.0,<3.0.0", ] requires-python = ">=3.10" readme = "README.rst" @@ -30,6 +28,14 @@ classifiers = [ "Topic :: Internet :: Proxy Servers", ] +[project.optional-dependencies] +aiohttp = [ + "aiohttp>=3.14.1,<4.0.0", +] +requests = [ + "requests>=2.24.0,<3.0.0", +] + [dependency-groups] dev = [ "pytest>=9.1.1", @@ -115,6 +121,10 @@ commands = [ [tool.tox.env.lint] description = "Code linting" python = "3.14" +extras = [ + "aiohttp", + "requests", +] dependency_groups = [ "dev", "lint", diff --git a/src/geoip2/webservice.py b/src/geoip2/webservice.py index cc34284..f296308 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,7 +352,15 @@ 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( headers={ "Accept": "application/json", @@ -360,7 +368,7 @@ async def _session(self) -> aiohttp.ClientSession: self._account_id, self._license_key, ), - "User-Agent": _AIOHTTP_UA, + "User-Agent": user_agent, }, timeout=aiohttp.ClientTimeout(total=self._timeout), ) @@ -474,7 +482,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/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 1142684..439906a 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -1,11 +1,8 @@ import datetime 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 59a2600..d6118d9 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, @@ -405,6 +403,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 @@ -418,6 +417,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") diff --git a/uv.lock b/uv.lock index 600f0c1..5ef2e6f 100644 --- a/uv.lock +++ b/uv.lock @@ -441,8 +441,14 @@ name = "geoip2" version = "5.3.0" source = { editable = "." } dependencies = [ - { name = "aiohttp" }, { name = "maxminddb" }, +] + +[package.optional-dependencies] +aiohttp = [ + { name = "aiohttp" }, +] +requests = [ { name = "requests" }, ] @@ -459,10 +465,11 @@ lint = [ [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = ">=3.14.1,<4.0.0" }, + { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.14.1,<4.0.0" }, { name = "maxminddb", specifier = ">=3.0.0,<4.0.0" }, - { name = "requests", specifier = ">=2.24.0,<3.0.0" }, + { name = "requests", marker = "extra == 'requests'", specifier = ">=2.24.0,<3.0.0" }, ] +provides-extras = ["aiohttp", "requests"] [package.metadata.requires-dev] dev = [