From 2e17f8f8f50c088d4e9594d7c56e4a72121683a0 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 13:11:30 -0700 Subject: [PATCH 01/11] Switch to ruff for formatting --- pyproject.toml | 11 +---- tests/database_test.py | 1 - tests/webservice_test.py | 1 - uv.lock | 97 +++++++++++++++------------------------- 4 files changed, 36 insertions(+), 74 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6be12846..64ea7869 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ dev = [ "types-requests>=2.32.0.20250328", ] lint = [ - "black>=25.1.0", "flake8>=7.2.0", "mypy>=1.15.0", "pylint>=3.3.6", @@ -56,11 +55,6 @@ Documentation = "https://geoip2.readthedocs.org/" "Source Code" = "https://github.com/maxmind/GeoIP2-python" "Issue Tracker" = "https://github.com/maxmind/GeoIP2-python/issues" -[tool.black] -# src is showing up in our GitHub linting builds. It seems to -# contain deps. -extend-exclude = '^/src/' - [tool.pylint."MESSAGES CONTROL"] disable = "duplicate-code" @@ -73,9 +67,6 @@ ignore = [ # documenting magic methods "D105", - # Line length. We let black handle this for now. - "E501", - # Don't bother with future imports for type annotations "FA100", @@ -121,10 +112,10 @@ dependency_groups = [ "lint", ] commands = [ - ["black", "--check", "--diff", "."], ["flake8", "geoip2"], ["mypy", "geoip2", "tests"], ["pylint", "geoip2"], + ["ruff", "format", "--check", "--diff", "."], ] [tool.tox.gh.python] diff --git a/tests/database_test.py b/tests/database_test.py index 00d54a26..81ce6e40 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -91,7 +91,6 @@ def test_anonymous_plus(self) -> None: with geoip2.database.Reader( "tests/data/test-data/GeoIP-Anonymous-Plus-Test.mmdb", ) as reader: - ip_address = "1.2.0.1" record = reader.anonymous_plus(ip_address) diff --git a/tests/webservice_test.py b/tests/webservice_test.py index c17f86b8..19743613 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -387,7 +387,6 @@ def test_named_constructor_args(self) -> None: def test_missing_constructor_args(self) -> None: with self.assertRaises(TypeError): - self.client_class(license_key="1234567890ab") # type: ignore[call-arg] with self.assertRaises(TypeError): diff --git a/uv.lock b/uv.lock index e50b608e..5636ccc5 100644 --- a/uv.lock +++ b/uv.lock @@ -156,44 +156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - [[package]] name = "certifi" version = "2025.1.31" @@ -277,18 +239,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -316,6 +266,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] +[[package]] +name = "flake8" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, +] + [[package]] name = "frozenlist" version = "1.6.0" @@ -429,7 +393,7 @@ wheels = [ [[package]] name = "geoip2" -version = "5.0.1" +version = "5.1.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, @@ -444,7 +408,7 @@ dev = [ { name = "types-requests" }, ] lint = [ - { name = "black" }, + { name = "flake8" }, { name = "mypy" }, { name = "pylint" }, { name = "ruff" }, @@ -464,7 +428,7 @@ dev = [ { name = "types-requests", specifier = ">=2.32.0.20250328" }, ] lint = [ - { name = "black", specifier = ">=25.1.0" }, + { name = "flake8", specifier = ">=7.2.0" }, { name = "mypy", specifier = ">=1.15.0" }, { name = "pylint", specifier = ">=3.3.6" }, { name = "ruff", specifier = ">=0.11.6" }, @@ -846,15 +810,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - [[package]] name = "platformdirs" version = "4.3.7" @@ -978,6 +933,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, ] +[[package]] +name = "pycodestyle" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, +] + [[package]] name = "pylint" version = "3.3.6" From aeaeb39d49c0faac17959bba0d0476bbe074da33 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 13:25:01 -0700 Subject: [PATCH 02/11] Run ruff fix --- geoip2/database.py | 18 +++++++++--------- geoip2/errors.py | 2 +- geoip2/webservice.py | 12 ++++++------ tests/webservice_test.py | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/geoip2/database.py b/geoip2/database.py index b36a0e41..231be462 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -132,7 +132,7 @@ def country(self, ip_address: IPAddress) -> Country: """ return cast( - Country, + "Country", self._model_for(geoip2.models.Country, "Country", ip_address), ) @@ -144,7 +144,7 @@ def city(self, ip_address: IPAddress) -> City: :returns: :py:class:`geoip2.models.City` object """ - return cast(City, self._model_for(geoip2.models.City, "City", ip_address)) + return cast("City", self._model_for(geoip2.models.City, "City", ip_address)) def anonymous_ip(self, ip_address: IPAddress) -> AnonymousIP: """Get the AnonymousIP object for the IP address. @@ -155,7 +155,7 @@ def anonymous_ip(self, ip_address: IPAddress) -> AnonymousIP: """ return cast( - AnonymousIP, + "AnonymousIP", self._flat_model_for( geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", @@ -172,7 +172,7 @@ def anonymous_plus(self, ip_address: IPAddress) -> AnonymousPlus: """ return cast( - AnonymousPlus, + "AnonymousPlus", self._flat_model_for( geoip2.models.AnonymousPlus, "GeoIP-Anonymous-Plus", @@ -189,7 +189,7 @@ def asn(self, ip_address: IPAddress) -> ASN: """ return cast( - ASN, + "ASN", self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address), ) @@ -202,7 +202,7 @@ def connection_type(self, ip_address: IPAddress) -> ConnectionType: """ return cast( - ConnectionType, + "ConnectionType", self._flat_model_for( geoip2.models.ConnectionType, "GeoIP2-Connection-Type", @@ -219,7 +219,7 @@ def domain(self, ip_address: IPAddress) -> Domain: """ return cast( - Domain, + "Domain", self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address), ) @@ -232,7 +232,7 @@ def enterprise(self, ip_address: IPAddress) -> Enterprise: """ return cast( - Enterprise, + "Enterprise", self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address), ) @@ -245,7 +245,7 @@ def isp(self, ip_address: IPAddress) -> ISP: """ return cast( - ISP, + "ISP", self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address), ) diff --git a/geoip2/errors.py b/geoip2/errors.py index 3e2f9bd4..b5cc27e3 100644 --- a/geoip2/errors.py +++ b/geoip2/errors.py @@ -42,7 +42,7 @@ def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network if self.ip_address is None or self._prefix_len is None: return None return ipaddress.ip_network( - f"{self.ip_address}/{self._prefix_len}", strict=False + f"{self.ip_address}/{self._prefix_len}", strict=False, ) diff --git a/geoip2/webservice.py b/geoip2/webservice.py index f910fcae..526e4439 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -304,7 +304,7 @@ async def city(self, ip_address: IPAddress = "me") -> City: """ return cast( - City, + "City", await self._response_for("city", geoip2.models.City, ip_address), ) @@ -319,7 +319,7 @@ async def country(self, ip_address: IPAddress = "me") -> Country: """ return cast( - Country, + "Country", await self._response_for("country", geoip2.models.Country, ip_address), ) @@ -337,7 +337,7 @@ async def insights(self, ip_address: IPAddress = "me") -> Insights: """ return cast( - Insights, + "Insights", await self._response_for("insights", geoip2.models.Insights, ip_address), ) @@ -468,7 +468,7 @@ def city(self, ip_address: IPAddress = "me") -> City: :returns: :py:class:`geoip2.models.City` object """ - return cast(City, self._response_for("city", geoip2.models.City, ip_address)) + return cast("City", self._response_for("city", geoip2.models.City, ip_address)) def country(self, ip_address: IPAddress = "me") -> Country: """Call the GeoIP2 Country endpoint with the specified IP. @@ -481,7 +481,7 @@ def country(self, ip_address: IPAddress = "me") -> Country: """ return cast( - Country, + "Country", self._response_for("country", geoip2.models.Country, ip_address), ) @@ -499,7 +499,7 @@ def insights(self, ip_address: IPAddress = "me") -> Insights: """ return cast( - Insights, + "Insights", self._response_for("insights", geoip2.models.Insights, ip_address), ) diff --git a/tests/webservice_test.py b/tests/webservice_test.py index 19743613..41a37439 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -7,7 +7,7 @@ import unittest from abc import ABC, abstractmethod from collections import defaultdict -from typing import cast, Callable, Union +from typing import Callable, Union, cast import pytest import pytest_httpserver @@ -54,7 +54,7 @@ class TestBaseClient(unittest.TestCase, ABC): # this is not a comprehensive representation of the # JSON from the server - insights = cast(dict, copy.deepcopy(country)) + insights = cast("dict", copy.deepcopy(country)) insights["traits"]["user_count"] = 2 insights["traits"]["static_ip_score"] = 1.3 From 7b51784ef00036eb4adfa0a02f795e14121ee95d Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 13:25:40 -0700 Subject: [PATCH 03/11] Apply ruff unsafe fixes --- geoip2/database.py | 6 ++++-- tests/models_test.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/geoip2/database.py b/geoip2/database.py index 231be462..05cf62f1 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -252,13 +252,15 @@ def isp(self, ip_address: IPAddress) -> ISP: def _get(self, database_type: str, ip_address: IPAddress) -> Any: if database_type not in self._db_type: caller = inspect.stack()[2][3] + msg = f"The {caller} method cannot be used with the {self._db_type} database" raise TypeError( - f"The {caller} method cannot be used with the {self._db_type} database", + msg, ) (record, prefix_len) = self._db_reader.get_with_prefix_len(ip_address) if record is None: + msg = f"The address {ip_address} is not in the database." raise geoip2.errors.AddressNotFoundError( - f"The address {ip_address} is not in the database.", + msg, str(ip_address), prefix_len, ) diff --git a/tests/models_test.py b/tests/models_test.py index 58bff6e8..7584e234 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -395,7 +395,7 @@ def test_city_full(self) -> None: r"^geoip2.models.City\(\[.*en.*\], .*geoname_id.*\)", ) - self.assertFalse(model == True, "__eq__ does not blow up on weird input") + self.assertFalse(model is True, "__eq__ does not blow up on weird input") def test_unknown_keys(self) -> None: model = geoip2.models.City( From 382085ea426b4e700157dd5019b63a0f00f29c95 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 13:27:12 -0700 Subject: [PATCH 04/11] Do not lint docs dir --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 64ea7869..72b8e949 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] +"docs/*" = ["ALL"] "geoip2/{models,records}.py" = [ "D107", "PLR0913" ] "tests/*" = ["ANN201", "D"] From a1c5ad8179b385e31317a350dc17eb25e2963ffe Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 14:23:45 -0700 Subject: [PATCH 05/11] Improve type hinting --- geoip2/_internal.py | 2 +- geoip2/database.py | 70 ++++++++------ geoip2/errors.py | 28 +++--- geoip2/models.py | 139 ++++++++++++++------------- geoip2/records.py | 203 ++++++++++++++++++++------------------- geoip2/webservice.py | 70 ++++++++------ tests/database_test.py | 4 +- tests/models_test.py | 10 +- tests/webservice_test.py | 29 +++--- 9 files changed, 297 insertions(+), 258 deletions(-) diff --git a/geoip2/_internal.py b/geoip2/_internal.py index 0918feaa..e34d8e8f 100644 --- a/geoip2/_internal.py +++ b/geoip2/_internal.py @@ -10,7 +10,7 @@ class Model(metaclass=ABCMeta): def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and self.to_dict() == other.to_dict() - def __ne__(self, other) -> bool: + def __ne__(self, other: object) -> bool: return not self.__eq__(other) # pylint: disable=too-many-branches diff --git a/geoip2/database.py b/geoip2/database.py index 05cf62f1..601ad807 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -1,9 +1,9 @@ """The database reader for MaxMind MMDB files.""" +from __future__ import annotations + import inspect -import os -from collections.abc import Sequence -from typing import IO, Any, AnyStr, Optional, Union, cast +from typing import IO, TYPE_CHECKING, AnyStr, cast import maxminddb from maxminddb import ( @@ -15,21 +15,30 @@ MODE_MMAP_EXT, ) +from maxminddb import InvalidDatabaseError + import geoip2 import geoip2.errors import geoip2.models -from geoip2.models import ( - ASN, - ISP, - AnonymousIP, - AnonymousPlus, - City, - ConnectionType, - Country, - Domain, - Enterprise, -) -from geoip2.types import IPAddress + +if TYPE_CHECKING: + import os + from collections.abc import Sequence + + from typing_extensions import Self + + from geoip2.models import ( + ASN, + ISP, + AnonymousIP, + AnonymousPlus, + City, + ConnectionType, + Country, + Domain, + Enterprise, + ) + from geoip2.types import IPAddress __all__ = [ "MODE_AUTO", @@ -67,8 +76,8 @@ class Reader: def __init__( self, - fileish: Union[AnyStr, int, os.PathLike, IO], - locales: Optional[Sequence[str]] = None, + fileish: AnyStr | int | os.PathLike | IO, + locales: Sequence[str] | None = None, mode: int = MODE_AUTO, ) -> None: """Create GeoIP2 Reader. @@ -117,10 +126,10 @@ def __init__( self._db_type = self._db_reader.metadata().database_type self._locales = locales - def __enter__(self) -> "Reader": + def __enter__(self) -> Self: return self - def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: + def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: self.close() def country(self, ip_address: IPAddress) -> Country: @@ -249,10 +258,12 @@ def isp(self, ip_address: IPAddress) -> ISP: self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address), ) - def _get(self, database_type: str, ip_address: IPAddress) -> Any: + def _get(self, database_type: str, ip_address: IPAddress) -> tuple[dict, int]: if database_type not in self._db_type: caller = inspect.stack()[2][3] - msg = f"The {caller} method cannot be used with the {self._db_type} database" + msg = ( + f"The {caller} method cannot be used with the {self._db_type} database" + ) raise TypeError( msg, ) @@ -264,14 +275,17 @@ def _get(self, database_type: str, ip_address: IPAddress) -> Any: str(ip_address), prefix_len, ) + if not isinstance(record, dict): + msg = f"Expected record to be a dict but was f{type(record)}" + raise InvalidDatabaseError(msg) return record, prefix_len def _model_for( self, - model_class: Union[type[Country], type[Enterprise], type[City]], + model_class: type[City | Country | Enterprise], types: str, ip_address: IPAddress, - ) -> Union[Country, Enterprise, City]: + ) -> City | Country | Enterprise: (record, prefix_len) = self._get(types, ip_address) return model_class( self._locales, @@ -282,16 +296,10 @@ def _model_for( def _flat_model_for( self, - model_class: Union[ - type[Domain], - type[ISP], - type[ConnectionType], - type[ASN], - type[AnonymousIP], - ], + model_class: type[Domain | ISP | ConnectionType | ASN | AnonymousIP], types: str, ip_address: IPAddress, - ) -> Union[ConnectionType, ISP, AnonymousIP, Domain, ASN]: + ) -> ConnectionType | ISP | AnonymousIP | Domain | ASN: (record, prefix_len) = self._get(types, ip_address) return model_class(ip_address, prefix_len=prefix_len, **record) diff --git a/geoip2/errors.py b/geoip2/errors.py index b5cc27e3..49d4bb50 100644 --- a/geoip2/errors.py +++ b/geoip2/errors.py @@ -1,7 +1,8 @@ """Typed errors thrown by this library.""" +from __future__ import annotations + import ipaddress -from typing import Optional, Union class GeoIP2Error(RuntimeError): @@ -16,24 +17,24 @@ class GeoIP2Error(RuntimeError): class AddressNotFoundError(GeoIP2Error): """The address you were looking up was not found.""" - ip_address: Optional[str] + ip_address: str | None """The IP address used in the lookup. This is only available for database lookups. """ - _prefix_len: Optional[int] + _prefix_len: int | None def __init__( self, message: str, - ip_address: Optional[str] = None, - prefix_len: Optional[int] = None, + ip_address: str | None = None, + prefix_len: int | None = None, ) -> None: super().__init__(message) self.ip_address = ip_address self._prefix_len = prefix_len @property - def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]: + def network(self) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: """The network associated with the error. In particular, this is the largest network where no address would be @@ -42,7 +43,8 @@ def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network if self.ip_address is None or self._prefix_len is None: return None return ipaddress.ip_network( - f"{self.ip_address}/{self._prefix_len}", strict=False, + f"{self.ip_address}/{self._prefix_len}", + strict=False, ) @@ -58,19 +60,19 @@ class HTTPError(GeoIP2Error): """ - http_status: Optional[int] + http_status: int | None """The HTTP status code returned""" - uri: Optional[str] + uri: str | None """The URI queried""" - decoded_content: Optional[str] + decoded_content: str | None """The decoded response content""" def __init__( self, message: str, - http_status: Optional[int] = None, - uri: Optional[str] = None, - decoded_content: Optional[str] = None, + http_status: int | None = None, + uri: str | None = None, + decoded_content: str | None = None, ) -> None: super().__init__(message) self.http_status = http_status diff --git a/geoip2/models.py b/geoip2/models.py index e045c8da..18526c8d 100644 --- a/geoip2/models.py +++ b/geoip2/models.py @@ -5,17 +5,22 @@ https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. """ +from __future__ import annotations + # pylint: disable=too-many-instance-attributes,too-few-public-methods,too-many-arguments import datetime import ipaddress from abc import ABCMeta -from collections.abc import Sequence from ipaddress import IPv4Address, IPv6Address -from typing import Optional, Union +from typing import TYPE_CHECKING import geoip2.records from geoip2._internal import Model -from geoip2.types import IPAddress + +if TYPE_CHECKING: + from collections.abc import Sequence + + from geoip2.types import IPAddress class Country(Model): @@ -49,16 +54,16 @@ class Country(Model): def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - continent: Optional[dict] = None, - country: Optional[dict] = None, - ip_address: Optional[IPAddress] = None, - maxmind: Optional[dict] = None, - prefix_len: Optional[int] = None, - registered_country: Optional[dict] = None, - represented_country: Optional[dict] = None, - traits: Optional[dict] = None, + continent: dict | None = None, + country: dict | None = None, + ip_address: IPAddress | None = None, + maxmind: dict | None = None, + prefix_len: int | None = None, + registered_country: dict | None = None, + represented_country: dict | None = None, + traits: dict | None = None, **_, ) -> None: self._locales = locales @@ -109,20 +114,20 @@ class City(Country): def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - city: Optional[dict] = None, - continent: Optional[dict] = None, - country: Optional[dict] = None, - location: Optional[dict] = None, - ip_address: Optional[IPAddress] = None, - maxmind: Optional[dict] = None, - postal: Optional[dict] = None, - prefix_len: Optional[int] = None, - registered_country: Optional[dict] = None, - represented_country: Optional[dict] = None, - subdivisions: Optional[list[dict]] = None, - traits: Optional[dict] = None, + city: dict | None = None, + continent: dict | None = None, + country: dict | None = None, + location: dict | None = None, + ip_address: IPAddress | None = None, + maxmind: dict | None = None, + postal: dict | None = None, + prefix_len: int | None = None, + registered_country: dict | None = None, + represented_country: dict | None = None, + subdivisions: list[dict] | None = None, + traits: dict | None = None, **_, ) -> None: super().__init__( @@ -154,14 +159,14 @@ class SimpleModel(Model, metaclass=ABCMeta): """Provides basic methods for non-location models.""" _ip_address: IPAddress - _network: Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]] - _prefix_len: Optional[int] + _network: ipaddress.IPv4Network | ipaddress.IPv6Network | None + _prefix_len: int | None def __init__( self, ip_address: IPAddress, - network: Optional[str], - prefix_len: Optional[int], + network: str | None, + prefix_len: int | None, ) -> None: if network: self._network = ipaddress.ip_network(network, strict=False) @@ -186,14 +191,14 @@ def __repr__(self) -> str: ) @property - def ip_address(self) -> Union[IPv4Address, IPv6Address]: + def ip_address(self) -> IPv4Address | IPv6Address: """The IP address for the record.""" if not isinstance(self._ip_address, (IPv4Address, IPv6Address)): self._ip_address = ipaddress.ip_address(self._ip_address) return self._ip_address @property - def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]: + def network(self) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: """The network associated with the record. In particular, this is the largest network where all of the fields besides @@ -254,8 +259,8 @@ def __init__( is_public_proxy: bool = False, is_residential_proxy: bool = False, is_tor_exit_node: bool = False, - network: Optional[str] = None, - prefix_len: Optional[int] = None, + network: str | None = None, + prefix_len: int | None = None, **_, ) -> None: super().__init__(ip_address, network, prefix_len) @@ -270,17 +275,17 @@ def __init__( class AnonymousPlus(AnonymousIP): """Model class for the GeoIP Anonymous Plus.""" - anonymizer_confidence: Optional[int] + anonymizer_confidence: int | None """A score ranging from 1 to 99 that is our percent confidence that the network is currently part of an actively used VPN service. """ - network_last_seen: Optional[datetime.date] + network_last_seen: datetime.date | None """The last day that the network was sighted in our analysis of anonymized networks. """ - provider_name: Optional[str] + provider_name: str | None """The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated with the network. """ @@ -289,17 +294,17 @@ def __init__( self, ip_address: IPAddress, *, - anonymizer_confidence: Optional[int] = None, + anonymizer_confidence: int | None = None, is_anonymous: bool = False, is_anonymous_vpn: bool = False, is_hosting_provider: bool = False, is_public_proxy: bool = False, is_residential_proxy: bool = False, is_tor_exit_node: bool = False, - network: Optional[str] = None, - network_last_seen: Optional[str] = None, - prefix_len: Optional[int] = None, - provider_name: Optional[str] = None, + network: str | None = None, + network_last_seen: str | None = None, + prefix_len: int | None = None, + provider_name: str | None = None, **_, ) -> None: super().__init__( @@ -322,10 +327,10 @@ def __init__( class ASN(SimpleModel): """Model class for the GeoLite2 ASN.""" - autonomous_system_number: Optional[int] + autonomous_system_number: int | None """The autonomous system number associated with the IP address.""" - autonomous_system_organization: Optional[str] + autonomous_system_organization: str | None """The organization associated with the registered autonomous system number for the IP address. """ @@ -335,10 +340,10 @@ def __init__( self, ip_address: IPAddress, *, - autonomous_system_number: Optional[int] = None, - autonomous_system_organization: Optional[str] = None, - network: Optional[str] = None, - prefix_len: Optional[int] = None, + autonomous_system_number: int | None = None, + autonomous_system_organization: str | None = None, + network: str | None = None, + prefix_len: int | None = None, **_, ) -> None: super().__init__(ip_address, network, prefix_len) @@ -349,7 +354,7 @@ def __init__( class ConnectionType(SimpleModel): """Model class for the GeoIP2 Connection-Type.""" - connection_type: Optional[str] + connection_type: str | None """The connection type may take the following values: - Dialup @@ -365,9 +370,9 @@ def __init__( self, ip_address: IPAddress, *, - connection_type: Optional[str] = None, - network: Optional[str] = None, - prefix_len: Optional[int] = None, + connection_type: str | None = None, + network: str | None = None, + prefix_len: int | None = None, **_, ) -> None: super().__init__(ip_address, network, prefix_len) @@ -377,16 +382,16 @@ def __init__( class Domain(SimpleModel): """Model class for the GeoIP2 Domain.""" - domain: Optional[str] + domain: str | None """The domain associated with the IP address.""" def __init__( self, ip_address: IPAddress, *, - domain: Optional[str] = None, - network: Optional[str] = None, - prefix_len: Optional[int] = None, + domain: str | None = None, + network: str | None = None, + prefix_len: int | None = None, **_, ) -> None: super().__init__(ip_address, network, prefix_len) @@ -396,22 +401,22 @@ def __init__( class ISP(ASN): """Model class for the GeoIP2 ISP.""" - isp: Optional[str] + isp: str | None """The name of the ISP associated with the IP address.""" - mobile_country_code: Optional[str] + mobile_country_code: str | None """The `mobile country code (MCC) `_ associated with the IP address and ISP. """ - mobile_network_code: Optional[str] + mobile_network_code: str | None """The `mobile network code (MNC) `_ associated with the IP address and ISP. """ - organization: Optional[str] + organization: str | None """The name of the organization associated with the IP address.""" # pylint:disable=too-many-arguments,too-many-positional-arguments @@ -419,14 +424,14 @@ def __init__( self, ip_address: IPAddress, *, - autonomous_system_number: Optional[int] = None, - autonomous_system_organization: Optional[str] = None, - isp: Optional[str] = None, - mobile_country_code: Optional[str] = None, - mobile_network_code: Optional[str] = None, - organization: Optional[str] = None, - network: Optional[str] = None, - prefix_len: Optional[int] = None, + autonomous_system_number: int | None = None, + autonomous_system_organization: str | None = None, + isp: str | None = None, + mobile_country_code: str | None = None, + mobile_network_code: str | None = None, + organization: str | None = None, + network: str | None = None, + prefix_len: int | None = None, **_, ) -> None: super().__init__( diff --git a/geoip2/records.py b/geoip2/records.py index 3da901a2..1ce74eb1 100644 --- a/geoip2/records.py +++ b/geoip2/records.py @@ -2,16 +2,23 @@ # pylint:disable=too-many-arguments,too-many-positional-arguments,too-many-instance-attributes,too-many-locals +from __future__ import annotations + import ipaddress # pylint:disable=R0903 from abc import ABCMeta -from collections.abc import Sequence from ipaddress import IPv4Address, IPv6Address -from typing import Optional, Union +from typing import TYPE_CHECKING from geoip2._internal import Model -from geoip2.types import IPAddress + +if TYPE_CHECKING: + from collections.abc import Sequence + + from typing_extensions import Self + + from geoip2.types import IPAddress class Record(Model, metaclass=ABCMeta): @@ -31,8 +38,8 @@ class PlaceRecord(Record, metaclass=ABCMeta): def __init__( self, - locales: Optional[Sequence[str]], - names: Optional[dict[str, str]], + locales: Sequence[str] | None, + names: dict[str, str] | None, ) -> None: if locales is None: locales = ["en"] @@ -42,7 +49,7 @@ def __init__( self.names = names @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """The name based on the locales list passed to the constructor.""" # pylint:disable=E1101 return next((self.names.get(x) for x in self._locales if x in self.names), None) @@ -56,21 +63,21 @@ class City(PlaceRecord): This record is returned by ``city``, ``enterprise``, and ``insights``. """ - confidence: Optional[int] + confidence: int | None """A value from 0-100 indicating MaxMind's confidence that the city is correct. This attribute is only available from the Insights end point and the Enterprise database. """ - geoname_id: Optional[int] + geoname_id: int | None """The GeoName ID for the city.""" def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - confidence: Optional[int] = None, - geoname_id: Optional[int] = None, - names: Optional[dict[str, str]] = None, + confidence: int | None = None, + geoname_id: int | None = None, + names: dict[str, str] | None = None, **_, ) -> None: self.confidence = confidence @@ -85,20 +92,20 @@ class Continent(PlaceRecord): address. """ - code: Optional[str] + code: str | None """A two character continent code like "NA" (North America) or "OC" (Oceania). """ - geoname_id: Optional[int] + geoname_id: int | None """The GeoName ID for the continent.""" def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - code: Optional[str] = None, - geoname_id: Optional[int] = None, - names: Optional[dict[str, str]] = None, + code: str | None = None, + geoname_id: int | None = None, + names: dict[str, str] | None = None, **_, ) -> None: self.code = code @@ -112,16 +119,16 @@ class Country(PlaceRecord): This class contains the country-level data associated with an IP address. """ - confidence: Optional[int] + confidence: int | None """A value from 0-100 indicating MaxMind's confidence that the country is correct. This attribute is only available from the Insights end point and the Enterprise database. """ - geoname_id: Optional[int] + geoname_id: int | None """The GeoName ID for the country.""" is_in_european_union: bool """This is true if the country is a member state of the European Union.""" - iso_code: Optional[str] + iso_code: str | None """The two-character `ISO 3166-1 `_ alpha code for the country. @@ -129,13 +136,13 @@ class Country(PlaceRecord): def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - confidence: Optional[int] = None, - geoname_id: Optional[int] = None, + confidence: int | None = None, + geoname_id: int | None = None, is_in_european_union: bool = False, - iso_code: Optional[str] = None, - names: Optional[dict[str, str]] = None, + iso_code: str | None = None, + names: dict[str, str] | None = None, **_, ) -> None: self.confidence = confidence @@ -153,7 +160,7 @@ class RepresentedCountry(Country): represented by something like a military base. """ - type: Optional[str] + type: str | None """A string indicating the type of entity that is representing the country. Currently we only return ``military`` but this could expand to include other types in the future. @@ -161,15 +168,15 @@ class RepresentedCountry(Country): def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - confidence: Optional[int] = None, - geoname_id: Optional[int] = None, + confidence: int | None = None, + geoname_id: int | None = None, is_in_european_union: bool = False, - iso_code: Optional[str] = None, - names: Optional[dict[str, str]] = None, + iso_code: str | None = None, + names: dict[str, str] | None = None, # pylint:disable=redefined-builtin - type: Optional[str] = None, + type: str | None = None, **_, ) -> None: self.type = type @@ -191,37 +198,37 @@ class Location(Record): This record is returned by ``city``, ``enterprise``, and ``insights``. """ - average_income: Optional[int] + average_income: int | None """The average income in US dollars associated with the requested IP address. This attribute is only available from the Insights end point. """ - accuracy_radius: Optional[int] + accuracy_radius: int | None """The approximate accuracy radius in kilometers around the latitude and longitude for the IP address. This is the radius where we have a 67% confidence that the device using the IP address resides within the circle centered at the latitude and longitude with the provided radius. """ - latitude: Optional[float] + latitude: float | None """The approximate latitude of the location associated with the IP address. This value is not precise and should not be used to identify a particular address or household. """ - longitude: Optional[float] + longitude: float | None """The approximate longitude of the location associated with the IP address. This value is not precise and should not be used to identify a particular address or household. """ - metro_code: Optional[int] + metro_code: int | None """The metro code is a no-longer-maintained code for targeting advertisements in Google. .. deprecated:: 4.9.0 """ - population_density: Optional[int] + population_density: int | None """The estimated population per square kilometer associated with the IP address. This attribute is only available from the Insights end point. """ - time_zone: Optional[str] + time_zone: str | None """The time zone associated with location, as specified by the `IANA Time Zone Database `_, e.g., "America/New_York". @@ -230,13 +237,13 @@ class Location(Record): def __init__( self, *, - average_income: Optional[int] = None, - accuracy_radius: Optional[int] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - metro_code: Optional[int] = None, - population_density: Optional[int] = None, - time_zone: Optional[str] = None, + average_income: int | None = None, + accuracy_radius: int | None = None, + latitude: float | None = None, + longitude: float | None = None, + metro_code: int | None = None, + population_density: int | None = None, + time_zone: str | None = None, **_, ) -> None: self.average_income = average_income @@ -251,12 +258,12 @@ def __init__( class MaxMind(Record): """Contains data related to your MaxMind account.""" - queries_remaining: Optional[int] + queries_remaining: int | None """The number of remaining queries you have for the end point you are calling. """ - def __init__(self, *, queries_remaining: Optional[int] = None, **_) -> None: + def __init__(self, *, queries_remaining: int | None = None, **_) -> None: self.queries_remaining = queries_remaining @@ -268,12 +275,12 @@ class Postal(Record): This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ - code: Optional[str] + code: str | None """The postal code of the location. Postal codes are not available for all countries. In some countries, this will only contain part of the postal code. """ - confidence: Optional[int] + confidence: int | None """A value from 0-100 indicating MaxMind's confidence that the postal code is correct. This attribute is only available from the Insights end point and the Enterprise database. @@ -282,8 +289,8 @@ class Postal(Record): def __init__( self, *, - code: Optional[str] = None, - confidence: Optional[int] = None, + code: str | None = None, + confidence: int | None = None, **_, ) -> None: self.code = code @@ -298,26 +305,26 @@ class Subdivision(PlaceRecord): This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ - confidence: Optional[int] + confidence: int | None """This is a value from 0-100 indicating MaxMind's confidence that the subdivision is correct. This attribute is only available from the Insights end point and the Enterprise database. """ - geoname_id: Optional[int] + geoname_id: int | None """This is a GeoName ID for the subdivision.""" - iso_code: Optional[str] + iso_code: str | None """This is a string up to three characters long contain the subdivision portion of the `ISO 3166-2 code `_. """ def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - confidence: Optional[int] = None, - geoname_id: Optional[int] = None, - iso_code: Optional[str] = None, - names: Optional[dict[str, str]] = None, + confidence: int | None = None, + geoname_id: int | None = None, + iso_code: str | None = None, + names: dict[str, str] | None = None, **_, ) -> None: self.confidence = confidence @@ -339,17 +346,17 @@ class Subdivisions(tuple): """ def __new__( - cls: type["Subdivisions"], - locales: Optional[Sequence[str]], - *subdivisions, - ) -> "Subdivisions": + cls: type[Self], + locales: Sequence[str] | None, + *subdivisions: dict, + ) -> Self: subobjs = tuple(Subdivision(locales, **x) for x in subdivisions) - return super().__new__(cls, subobjs) # type: ignore + return super().__new__(cls, subobjs) def __init__( self, - locales: Optional[Sequence[str]], - *subdivisions, # pylint:disable=W0613 + locales: Sequence[str] | None, + *_: dict, ) -> None: self._locales = locales super().__init__() @@ -373,19 +380,19 @@ class Traits(Record): This class contains the traits data associated with an IP address. """ - autonomous_system_number: Optional[int] + autonomous_system_number: int | None """The `autonomous system number `_ associated with the IP address. This attribute is only available from the City Plus and Insights web services and the Enterprise database. """ - autonomous_system_organization: Optional[str] + autonomous_system_organization: str | None """The organization associated with the registered `autonomous system number `_ for the IP address. This attribute is only available from the City Plus and Insights web service end points and the Enterprise database. """ - connection_type: Optional[str] + connection_type: str | None """The connection type may take the following values: - Dialup @@ -399,14 +406,14 @@ class Traits(Record): This attribute is only available from the City Plus and Insights web service end points and the Enterprise database. """ - domain: Optional[str] + domain: str | None """The second level domain associated with the IP address. This will be something like "example.com" or "example.co.uk", not "foo.example.com". This attribute is only available from the City Plus and Insights web service end points and the Enterprise database. """ - _ip_address: Optional[IPAddress] + _ip_address: IPAddress | None is_anonymous: bool """This is true if the IP address belongs to any sort of anonymous network. This attribute is only available from Insights. @@ -468,29 +475,29 @@ class Traits(Record): """This is true if the IP address is a Tor exit node. This attribute is only available from Insights. """ - isp: Optional[str] + isp: str | None """The name of the ISP associated with the IP address. This attribute is only available from the City Plus and Insights web services and the Enterprise database. """ - mobile_country_code: Optional[str] + mobile_country_code: str | None """The `mobile country code (MCC) `_ associated with the IP address and ISP. This attribute is available from the City Plus and Insights web services and the Enterprise database. """ - mobile_network_code: Optional[str] + mobile_network_code: str | None """The `mobile network code (MNC) `_ associated with the IP address and ISP. This attribute is available from the City Plus and Insights web services and the Enterprise database. """ - organization: Optional[str] + organization: str | None """The name of the organization associated with the IP address. This attribute is only available from the City Plus and Insights web services and the Enterprise database. """ - static_ip_score: Optional[float] + static_ip_score: float | None """An indicator of how static or dynamic an IP address is. The value ranges from 0 to 99.99 with higher values meaning a greater static association. For example, many IP addresses with a user_type of cellular have a @@ -501,13 +508,13 @@ class Traits(Record): the same user over time. This attribute is only available from Insights. """ - user_count: Optional[int] + user_count: int | None """The estimated number of users sharing the IP/network during the past 24 hours. For IPv4, the count is for the individual IP. For IPv6, the count is for the /64 network. This attribute is only available from Insights. """ - user_type: Optional[str] + user_type: str | None """The user type associated with the IP address. This can be one of the following values: @@ -531,16 +538,16 @@ class Traits(Record): This attribute is only available from the Insights end point and the Enterprise database. """ - _network: Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]] - _prefix_len: Optional[int] + _network: ipaddress.IPv4Network | ipaddress.IPv6Network | None + _prefix_len: int | None def __init__( self, *, - autonomous_system_number: Optional[int] = None, - autonomous_system_organization: Optional[str] = None, - connection_type: Optional[str] = None, - domain: Optional[str] = None, + autonomous_system_number: int | None = None, + autonomous_system_organization: str | None = None, + connection_type: str | None = None, + domain: str | None = None, is_anonymous: bool = False, is_anonymous_proxy: bool = False, is_anonymous_vpn: bool = False, @@ -550,16 +557,16 @@ def __init__( is_residential_proxy: bool = False, is_satellite_provider: bool = False, is_tor_exit_node: bool = False, - isp: Optional[str] = None, - ip_address: Optional[str] = None, - network: Optional[str] = None, - organization: Optional[str] = None, - prefix_len: Optional[int] = None, - static_ip_score: Optional[float] = None, - user_count: Optional[int] = None, - user_type: Optional[str] = None, - mobile_country_code: Optional[str] = None, - mobile_network_code: Optional[str] = None, + isp: str | None = None, + ip_address: str | None = None, + network: str | None = None, + organization: str | None = None, + prefix_len: int | None = None, + static_ip_score: float | None = None, + user_count: int | None = None, + user_type: str | None = None, + mobile_country_code: str | None = None, + mobile_network_code: str | None = None, is_anycast: bool = False, **_, ) -> None: @@ -595,7 +602,7 @@ def __init__( self._prefix_len = prefix_len @property - def ip_address(self) -> Optional[Union[IPv4Address, IPv6Address]]: + def ip_address(self) -> IPv4Address | IPv6Address | None: """The IP address that the data in the model is for. If you performed a "me" lookup against the web service, this will be @@ -613,7 +620,7 @@ def ip_address(self) -> Optional[Union[IPv4Address, IPv6Address]]: return ip_address @property - def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]: + def network(self) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: """The network associated with the record. In particular, this is the largest network where all of the fields besides diff --git a/geoip2/webservice.py b/geoip2/webservice.py index 526e4439..0e4a5d3d 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -21,10 +21,11 @@ """ +from __future__ import annotations + import ipaddress import json -from collections.abc import Sequence -from typing import Any, Optional, Union, cast +from typing import TYPE_CHECKING, cast import aiohttp import aiohttp.http @@ -42,8 +43,14 @@ OutOfQueriesError, PermissionRequiredError, ) -from geoip2.models import City, Country, Insights -from geoip2.types import IPAddress + +if TYPE_CHECKING: + from collections.abc import Sequence + + from typing_extensions import Self + + from geoip2.models import City, Country, Insights + from geoip2.types import IPAddress _AIOHTTP_UA = ( f"GeoIP2-Python-Client/{geoip2.__version__} {aiohttp.http.SERVER_SOFTWARE}" @@ -66,7 +73,7 @@ def __init__( account_id: int, license_key: str, host: str, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, timeout: float, ) -> None: """Construct a Client.""" @@ -90,7 +97,7 @@ def _uri(self, path: str, ip_address: IPAddress) -> str: return "/".join([self._base_uri, path, str(ip_address)]) @staticmethod - def _handle_success(body: str, uri: str) -> Any: + def _handle_success(body: str, uri: str) -> dict: try: return json.loads(body) except ValueError as ex: @@ -168,13 +175,13 @@ def _exception_for_web_service_error( code: str, status: int, uri: str, - ) -> Union[ - AuthenticationError, - AddressNotFoundError, - PermissionRequiredError, - OutOfQueriesError, - InvalidRequestError, - ]: + ) -> ( + AuthenticationError + | AddressNotFoundError + | PermissionRequiredError + | OutOfQueriesError + | InvalidRequestError + ): if code in ("IP_ADDRESS_NOT_FOUND", "IP_ADDRESS_RESERVED"): return AddressNotFoundError(message) if code in ( @@ -197,7 +204,7 @@ def _exception_for_web_service_error( def _exception_for_5xx_status( status: int, uri: str, - body: Optional[str], + body: str | None, ) -> HTTPError: return HTTPError( f"Received a server error ({status}) for {uri}", @@ -210,7 +217,7 @@ def _exception_for_5xx_status( def _exception_for_non_200_status( status: int, uri: str, - body: Optional[str], + body: str | None, ) -> HTTPError: return HTTPError( f"Received a very surprising HTTP status ({status}) for {uri}", @@ -273,16 +280,16 @@ class AsyncClient(BaseClient): """ _existing_session: aiohttp.ClientSession - _proxy: Optional[str] + _proxy: str | None def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, account_id: int, license_key: str, host: str = "geoip.maxmind.com", - locales: Optional[Sequence[str]] = None, + locales: Sequence[str] | None = None, timeout: float = 60, - proxy: Optional[str] = None, + proxy: str | None = None, ) -> None: super().__init__( account_id, @@ -354,9 +361,9 @@ async def _session(self) -> aiohttp.ClientSession: async def _response_for( self, path: str, - model_class: Union[type[Insights], type[City], type[Country]], + model_class: type[City | Country | Insights], ip_address: IPAddress, - ) -> Union[Country, City, Insights]: + ) -> Country | City | Insights: uri = self._uri(path, ip_address) session = await self._session() async with await session.get(uri, proxy=self._proxy) as response: @@ -376,10 +383,15 @@ async def close(self) -> None: if hasattr(self, "_existing_session"): await self._existing_session.close() - async def __aenter__(self) -> "AsyncClient": + async def __aenter__(self) -> Self: return self - async def __aexit__(self, exc_type: None, exc_value: None, traceback: None) -> None: + async def __aexit__( + self, + exc_type: object, + exc_value: object, + traceback: object, + ) -> None: await self.close() @@ -437,16 +449,16 @@ class Client(BaseClient): """ _session: requests.Session - _proxies: Optional[dict[str, str]] + _proxies: dict[str, str] | None def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, account_id: int, license_key: str, host: str = "geoip.maxmind.com", - locales: Optional[Sequence[str]] = None, + locales: Sequence[str] | None = None, timeout: float = 60, - proxy: Optional[str] = None, + proxy: str | None = None, ) -> None: super().__init__(account_id, license_key, host, locales, timeout) self._session = requests.Session() @@ -506,9 +518,9 @@ def insights(self, ip_address: IPAddress = "me") -> Insights: def _response_for( self, path: str, - model_class: Union[type[Insights], type[City], type[Country]], + model_class: type[City | Country | Insights], ip_address: IPAddress, - ) -> Union[Country, City, Insights]: + ) -> Country | City | Insights: uri = self._uri(path, ip_address) response = self._session.get(uri, proxies=self._proxies, timeout=self._timeout) status = response.status_code @@ -526,8 +538,8 @@ def close(self) -> None: """ self._session.close() - def __enter__(self) -> "Client": + def __enter__(self) -> Self: return self - def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: + def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: self.close() diff --git a/tests/database_test.py b/tests/database_test.py index 81ce6e40..4d04511b 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -17,7 +17,7 @@ try: import maxminddb.extension except ImportError: - maxminddb.extension = None # type: ignore + maxminddb.extension = None # type: ignore[assignment] class TestReader(unittest.TestCase): @@ -297,7 +297,7 @@ def test_context_manager(self) -> None: ) @patch("maxminddb.open_database") - def test_modes(self, mock_open) -> None: + def test_modes(self, mock_open: MagicMock) -> None: mock_open.return_value = MagicMock() path = "tests/data/test-data/GeoIP2-Country-Test.mmdb" diff --git a/tests/models_test.py b/tests/models_test.py index 7584e234..e7ce32a3 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python - - import ipaddress import sys import unittest +from typing import ClassVar sys.path.append("..") @@ -93,7 +91,7 @@ def test_insights_full(self) -> None: }, } - model = geoip2.models.Insights(["en"], **raw) # type: ignore + model = geoip2.models.Insights(["en"], **raw) # type: ignore[arg-type] self.assertEqual( type(model), geoip2.models.Insights, @@ -296,7 +294,7 @@ def test_city_full(self) -> None: "is_satellite_provider": True, }, } - model = geoip2.models.City(["en"], **raw) # type: ignore + model = geoip2.models.City(["en"], **raw) # type: ignore[arg-type] self.assertEqual(type(model), geoip2.models.City, "geoip2.models.City object") self.assertEqual( type(model.city), @@ -446,7 +444,7 @@ def test_unknown_keys(self) -> None: class TestNames(unittest.TestCase): - raw: dict = { + raw: ClassVar[dict] = { "continent": { "code": "NA", "geoname_id": 42, diff --git a/tests/webservice_test.py b/tests/webservice_test.py index 41a37439..a5189032 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +from __future__ import annotations + import asyncio import copy import ipaddress @@ -7,7 +9,7 @@ import unittest from abc import ABC, abstractmethod from collections import defaultdict -from typing import Callable, Union, cast +from typing import Callable, ClassVar, cast import pytest import pytest_httpserver @@ -28,10 +30,10 @@ class TestBaseClient(unittest.TestCase, ABC): - client: Union[AsyncClient, Client] - client_class: Callable[[int, str], Union[AsyncClient, Client]] + client: AsyncClient | Client + client_class: Callable[[int, str], AsyncClient | Client] - country = { + country: ClassVar = { "continent": {"code": "NA", "geoname_id": 42, "names": {"en": "North America"}}, "country": { "geoname_id": 1, @@ -59,9 +61,9 @@ class TestBaseClient(unittest.TestCase, ABC): insights["traits"]["static_ip_score"] = 1.3 @abstractmethod - def run_client(self, v): ... + def run_client(self, v): ... # noqa: ANN001 - def _content_type(self, endpoint): + def _content_type(self, endpoint: str) -> str: return ( "application/vnd.maxmind.com-" + endpoint @@ -280,7 +282,12 @@ def test_user_id_unkown(self) -> None: def test_out_of_queries_error(self) -> None: self._test_error(402, "OUT_OF_QUERIES", OutOfQueriesError) - def _test_error(self, status, error_code, error_class) -> None: + def _test_error( + self, + status: int, + error_code: str, + error_class: type[Exception], + ) -> None: msg = "Some error message" body = {"error": msg, "code": error_code} self.httpserver.expect_request( @@ -310,7 +317,7 @@ def test_unknown_error(self) -> None: self.run_client(self.client.country(ip)) def test_request(self) -> None: - def user_agent_compare(actual: str, expected: str) -> bool: + def user_agent_compare(actual: str, _: str) -> bool: if actual is None: return False return actual.startswith("GeoIP2-Python-Client/") @@ -390,7 +397,7 @@ def test_missing_constructor_args(self) -> None: self.client_class(license_key="1234567890ab") # type: ignore[call-arg] with self.assertRaises(TypeError): - self.client_class("47") # type: ignore + self.client_class("47") # type: ignore[call-arg,arg-type,misc] class TestClient(TestBaseClient): @@ -402,7 +409,7 @@ def setUp(self) -> None: self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") self.maxDiff = 20_000 - def run_client(self, v): + def run_client(self, v): # noqa: ANN001 return v @@ -420,7 +427,7 @@ def tearDown(self) -> None: self._loop.run_until_complete(self.client.close()) self._loop.close() - def run_client(self, v): + def run_client(self, v): # noqa: ANN001 return self._loop.run_until_complete(v) From f99a25484f2d4bfe2006f24a6ec8b675278245db Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 15:17:02 -0700 Subject: [PATCH 06/11] Add misc doc strings --- examples/benchmark.py | 3 ++- geoip2/__init__.py | 2 ++ geoip2/database.py | 4 ++-- geoip2/errors.py | 18 ++++++++++++++++++ geoip2/records.py | 20 ++++++++++++++++++++ geoip2/webservice.py | 4 ++++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/examples/benchmark.py b/examples/benchmark.py index b9fbc7c2..af678ade 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -1,5 +1,5 @@ #!/usr/bin/python - +"""Simple benchmarking script.""" import argparse import contextlib @@ -21,6 +21,7 @@ def lookup_ip_address() -> None: + """Look up IP address.""" ip = socket.inet_ntoa(struct.pack("!L", random.getrandbits(32))) with contextlib.suppress(geoip2.errors.AddressNotFoundError): reader.city(str(ip)) diff --git a/geoip2/__init__.py b/geoip2/__init__.py index 7cd957d1..0df20a8b 100644 --- a/geoip2/__init__.py +++ b/geoip2/__init__.py @@ -1,3 +1,5 @@ +"""geoip2 client library.""" + # pylint:disable=C0111 __title__ = "geoip2" diff --git a/geoip2/database.py b/geoip2/database.py index 601ad807..9fb839a5 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -306,12 +306,12 @@ def _flat_model_for( def metadata( self, ) -> maxminddb.reader.Metadata: - """The metadata for the open database. + """Get the metadata for the open database. :returns: :py:class:`maxminddb.reader.Metadata` object """ return self._db_reader.metadata() def close(self) -> None: - """Closes the GeoIP2 database.""" + """Close the GeoIP2 database.""" self._db_reader.close() diff --git a/geoip2/errors.py b/geoip2/errors.py index 49d4bb50..893d9e3f 100644 --- a/geoip2/errors.py +++ b/geoip2/errors.py @@ -29,6 +29,15 @@ def __init__( ip_address: str | None = None, prefix_len: int | None = None, ) -> None: + """Initialize self. + + Arguments: + message: A message describing the error. + ip_address: The IP address that was not found. + prefix_len: The prefix length for the network associated with + the IP address. + + """ super().__init__(message) self.ip_address = ip_address self._prefix_len = prefix_len @@ -74,6 +83,15 @@ def __init__( uri: str | None = None, decoded_content: str | None = None, ) -> None: + """Initialize self. + + Arguments: + message: A descriptive message for the error. + http_status: The HTTP status code associated with the error, if any. + uri: The URI that was being accessed when the error occurred. + decoded_content: The decoded HTTP response body, if available. + + """ super().__init__(message) self.http_status = http_status self.uri = uri diff --git a/geoip2/records.py b/geoip2/records.py index 1ce74eb1..8b3b2dd7 100644 --- a/geoip2/records.py +++ b/geoip2/records.py @@ -345,11 +345,30 @@ class Subdivisions(tuple): This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ + __slots__ = ("_locales",) + def __new__( cls: type[Self], locales: Sequence[str] | None, *subdivisions: dict, ) -> Self: + """Create a new Subdivisions instance. + + This method constructs the tuple with Subdivision objects created + from the provided dictionaries. + + Arguments: + cls: The class to instantiate (Subdivisions). + locales: A sequence of locale strings (e.g., ['en', 'fr']) + or None, passed to each Subdivision object. + *subdivisions: A variable number of dictionaries, where each + dictionary contains the data for a single :py:class:`Subdivision` + object (e.g., name, iso_code). + + Returns: + A new instance of Subdivisions containing :py:class:`Subdivision` objects. + + """ subobjs = tuple(Subdivision(locales, **x) for x in subdivisions) return super().__new__(cls, subobjs) @@ -358,6 +377,7 @@ def __init__( locales: Sequence[str] | None, *_: dict, ) -> None: + """Initialize the Subdivisions instance.""" self._locales = locales super().__init__() diff --git a/geoip2/webservice.py b/geoip2/webservice.py index 0e4a5d3d..9915c1ff 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -62,6 +62,8 @@ class BaseClient: # pylint: disable=missing-class-docstring, too-few-public-methods + """Base class for AsyncClient and Client.""" + _account_id: str _host: str _license_key: str @@ -291,6 +293,7 @@ def __init__( # pylint: disable=too-many-arguments,too-many-positional-argument timeout: float = 60, proxy: str | None = None, ) -> None: + """Initialize AsyncClient.""" super().__init__( account_id, license_key, @@ -460,6 +463,7 @@ def __init__( # pylint: disable=too-many-arguments,too-many-positional-argument timeout: float = 60, proxy: str | None = None, ) -> None: + """Initialize Client.""" super().__init__(account_id, license_key, host, locales, timeout) self._session = requests.Session() self._session.auth = (self._account_id, self._license_key) From 7ca009c2cb572d06169568f1409e0f2396f75057 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 15:18:33 -0700 Subject: [PATCH 07/11] Ignore and fix misc lints --- examples/benchmark.py | 3 ++- geoip2/_internal.py | 4 ++-- geoip2/database.py | 3 +-- geoip2/records.py | 6 ++---- geoip2/webservice.py | 11 ++++++----- tests/database_test.py | 25 ++++++++++++++++-------- tests/models_test.py | 42 +++++++++++++++++++++++++--------------- tests/webservice_test.py | 22 ++++++++++----------- 8 files changed, 66 insertions(+), 50 deletions(-) mode change 100644 => 100755 examples/benchmark.py diff --git a/examples/benchmark.py b/examples/benchmark.py old mode 100644 new mode 100755 index af678ade..e8e478ea --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -9,6 +9,7 @@ import timeit import geoip2.database +import geoip2.errors parser = argparse.ArgumentParser(description="Benchmark maxminddb.") parser.add_argument("--count", default=250000, type=int, help="number of lookups") @@ -33,4 +34,4 @@ def lookup_ip_address() -> None: number=args.count, ) -print(args.count / elapsed, "lookups per second") +print(args.count / elapsed, "lookups per second") # noqa: T201 diff --git a/geoip2/_internal.py b/geoip2/_internal.py index e34d8e8f..dd6fb65c 100644 --- a/geoip2/_internal.py +++ b/geoip2/_internal.py @@ -4,7 +4,7 @@ from abc import ABCMeta -class Model(metaclass=ABCMeta): +class Model(metaclass=ABCMeta): # noqa: B024 """Shared methods for MaxMind model classes.""" def __eq__(self, other: object) -> bool: @@ -14,7 +14,7 @@ def __ne__(self, other: object) -> bool: return not self.__eq__(other) # pylint: disable=too-many-branches - def to_dict(self) -> dict: + def to_dict(self) -> dict: # noqa: C901, PLR0912 """Return a dict of the object suitable for serialization.""" result = {} for key, value in self.__dict__.items(): diff --git a/geoip2/database.py b/geoip2/database.py index 9fb839a5..2c10538b 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -13,10 +13,9 @@ MODE_MEMORY, MODE_MMAP, MODE_MMAP_EXT, + InvalidDatabaseError, ) -from maxminddb import InvalidDatabaseError - import geoip2 import geoip2.errors import geoip2.models diff --git a/geoip2/records.py b/geoip2/records.py index 8b3b2dd7..9b2b15b0 100644 --- a/geoip2/records.py +++ b/geoip2/records.py @@ -176,7 +176,7 @@ def __init__( iso_code: str | None = None, names: dict[str, str] | None = None, # pylint:disable=redefined-builtin - type: str | None = None, + type: str | None = None, # noqa: A002 **_, ) -> None: self.type = type @@ -333,7 +333,7 @@ def __init__( super().__init__(locales, names) -class Subdivisions(tuple): +class Subdivisions(tuple): # noqa: SLOT001 """A tuple-like collection of subdivisions associated with an IP address. This class contains the subdivisions of the country associated with the @@ -345,8 +345,6 @@ class Subdivisions(tuple): This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ - __slots__ = ("_locales",) - def __new__( cls: type[Self], locales: Sequence[str] | None, diff --git a/geoip2/webservice.py b/geoip2/webservice.py index 9915c1ff..37d407e9 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -149,9 +149,10 @@ def _exception_for_4xx_status( decoded_body = json.loads(body) except ValueError as ex: return HTTPError( - f"Received a {status} error for {uri} but it did not include " - + "the expected JSON body: " - + ", ".join(ex.args), + ( + f"Received a {status} error for {uri} but it did not include " + f"the expected JSON body: {', '.join(ex.args)}" + ), status, uri, body, @@ -284,7 +285,7 @@ class AsyncClient(BaseClient): _existing_session: aiohttp.ClientSession _proxy: str | None - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments # noqa: PLR0913 self, account_id: int, license_key: str, @@ -454,7 +455,7 @@ class Client(BaseClient): _session: requests.Session _proxies: dict[str, str] | None - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments # noqa: PLR0913 self, account_id: int, license_key: str, diff --git a/tests/database_test.py b/tests/database_test.py index 4d04511b..2c831dff 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python - - import datetime import ipaddress import sys @@ -47,7 +44,7 @@ def test_unknown_address_network(self) -> None: self.fail("Expected AddressNotFoundError") except geoip2.errors.AddressNotFoundError as e: self.assertEqual(e.network, ipaddress.ip_network("10.0.0.0/8")) - except Exception as e: + except Exception as e: # noqa: BLE001 self.fail(f"Expected AddressNotFoundError, got {type(e)}: {e!s}") finally: reader.close() @@ -130,7 +127,11 @@ def test_asn(self) -> None: ip_address = "1.128.0.0" record = reader.asn(ip_address) - self.assertEqual(record, eval(repr(record)), "ASN repr can be eval'd") + self.assertEqual( + record, + eval(repr(record)), # noqa: S307 + "ASN repr can be eval'd", + ) self.assertEqual(record.autonomous_system_number, 1221) self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd") @@ -178,7 +179,7 @@ def test_connection_type(self) -> None: self.assertEqual( record, - eval(repr(record)), + eval(repr(record)), # noqa: S307 "ConnectionType repr can be eval'd", ) @@ -218,7 +219,11 @@ def test_domain(self) -> None: ip_address = "1.2.0.0" record = reader.domain(ip_address) - self.assertEqual(record, eval(repr(record)), "Domain repr can be eval'd") + self.assertEqual( + record, + eval(repr(record)), # noqa: S307 + "Domain repr can be eval'd", + ) self.assertEqual(record.domain, "maxmind.com") self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address)) @@ -266,7 +271,11 @@ def test_isp(self) -> None: ) as reader: ip_address = "1.128.0.0" record = reader.isp(ip_address) - self.assertEqual(record, eval(repr(record)), "ISP repr can be eval'd") + self.assertEqual( + record, + eval(repr(record)), # noqa: S307 + "ISP repr can be eval'd", + ) self.assertEqual(record.autonomous_system_number, 1221) self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd") diff --git a/tests/models_test.py b/tests/models_test.py index e7ce32a3..7cce9a8d 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -194,7 +194,11 @@ def test_insights_full(self) -> None: "Insights str representation looks reasonable", ) - self.assertEqual(model, eval(repr(model)), "Insights repr can be eval'd") + self.assertEqual( + model, + eval(repr(model)), # noqa: S307 + "Insights repr can be eval'd", + ) self.assertRegex( str(model.location), @@ -204,23 +208,29 @@ def test_insights_full(self) -> None: self.assertEqual( model.location, - eval(repr(model.location)), + eval(repr(model.location)), # noqa: S307 "Location repr can be eval'd", ) - self.assertIs(model.country.is_in_european_union, False) - self.assertIs(model.registered_country.is_in_european_union, False) - self.assertIs(model.represented_country.is_in_european_union, True) + self.assertIs(model.country.is_in_european_union, False) # noqa: FBT003 + self.assertIs( + model.registered_country.is_in_european_union, + False, # noqa: FBT003 + ) + self.assertIs( + model.represented_country.is_in_european_union, + True, # noqa: FBT003 + ) - self.assertIs(model.traits.is_anonymous, True) - self.assertIs(model.traits.is_anonymous_proxy, True) - self.assertIs(model.traits.is_anonymous_vpn, True) - self.assertIs(model.traits.is_anycast, True) - self.assertIs(model.traits.is_hosting_provider, True) - self.assertIs(model.traits.is_public_proxy, True) - self.assertIs(model.traits.is_residential_proxy, True) - self.assertIs(model.traits.is_satellite_provider, True) - self.assertIs(model.traits.is_tor_exit_node, True) + self.assertIs(model.traits.is_anonymous, True) # noqa: FBT003 + self.assertIs(model.traits.is_anonymous_proxy, True) # noqa: FBT003 + self.assertIs(model.traits.is_anonymous_vpn, True) # noqa: FBT003 + self.assertIs(model.traits.is_anycast, True) # noqa: FBT003 + self.assertIs(model.traits.is_hosting_provider, True) # noqa: FBT003 + self.assertIs(model.traits.is_public_proxy, True) # noqa: FBT003 + self.assertIs(model.traits.is_residential_proxy, True) # noqa: FBT003 + self.assertIs(model.traits.is_satellite_provider, True) # noqa: FBT003 + self.assertIs(model.traits.is_tor_exit_node, True) # noqa: FBT003 self.assertEqual(model.traits.user_count, 2) self.assertEqual(model.traits.static_ip_score, 1.3) @@ -433,9 +443,9 @@ def test_unknown_keys(self) -> None: unk_base={"blah": 1}, ) with self.assertRaises(AttributeError): - model.unk_base # type: ignore + model.unk_base # type: ignore[attr-defined] # noqa: B018 with self.assertRaises(AttributeError): - model.traits.invalid # type: ignore + model.traits.invalid # type: ignore[attr-defined] # noqa: B018 self.assertEqual( model.traits.ip_address, ipaddress.ip_address("1.2.3.4"), diff --git a/tests/webservice_test.py b/tests/webservice_test.py index a5189032..f9187538 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from __future__ import annotations import asyncio @@ -99,7 +97,7 @@ def test_country_ok(self) -> None: self.assertEqual(country.country.geoname_id, 1, "country geoname_id is 1") self.assertIs( country.country.is_in_european_union, - False, + False, # noqa: FBT003 "country is_in_european_union is False", ) self.assertEqual(country.country.iso_code, "US", "country iso_code is US") @@ -120,7 +118,7 @@ def test_country_ok(self) -> None: ) self.assertIs( country.registered_country.is_in_european_union, - True, + True, # noqa: FBT003 "registered_country is_in_european_union is True", ) self.assertEqual( @@ -273,10 +271,10 @@ def test_account_id_required(self) -> None: def test_user_id_required(self) -> None: self._test_error(401, "USER_ID_REQUIRED", AuthenticationError) - def test_account_id_unkown(self) -> None: + def test_account_id_unknown(self) -> None: self._test_error(401, "ACCOUNT_ID_UNKNOWN", AuthenticationError) - def test_user_id_unkown(self) -> None: + def test_user_id_unknown(self) -> None: self._test_error(401, "USER_ID_UNKNOWN", AuthenticationError) def test_out_of_queries_error(self) -> None: @@ -386,11 +384,11 @@ def test_insights_ok(self) -> None: self.assertEqual(insights.traits.user_count, 2, "user_count is 2") def test_named_constructor_args(self) -> None: - id = 47 + account_id = 47 key = "1234567890ab" - client = self.client_class(id, key) - self.assertEqual(client._account_id, str(id)) - self.assertEqual(client._license_key, key) + client = self.client_class(account_id, key) + self.assertEqual(client._account_id, str(account_id)) # noqa: SLF001 + self.assertEqual(client._license_key, key) # noqa: SLF001 def test_missing_constructor_args(self) -> None: with self.assertRaises(TypeError): @@ -406,7 +404,7 @@ class TestClient(TestBaseClient): def setUp(self) -> None: self.client_class = Client self.client = Client(42, "abcdef123456") - self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") + self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") # noqa: SLF001 self.maxDiff = 20_000 def run_client(self, v): # noqa: ANN001 @@ -420,7 +418,7 @@ def setUp(self) -> None: self._loop = asyncio.new_event_loop() self.client_class = AsyncClient self.client = AsyncClient(42, "abcdef123456") - self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") + self.client._base_uri = self.httpserver.url_for("/geoip/v2.1") # noqa: SLF001 self.maxDiff = 20_000 def tearDown(self) -> None: From 821315ba9b2778c026c122c987aca71ff970c550 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 15:19:59 -0700 Subject: [PATCH 08/11] Use ruff for linting Remove pylint and flake8. --- pyproject.toml | 5 +-- uv.lock | 113 ------------------------------------------------- 2 files changed, 1 insertion(+), 117 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 72b8e949..8784b26b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,9 +36,7 @@ dev = [ "types-requests>=2.32.0.20250328", ] lint = [ - "flake8>=7.2.0", "mypy>=1.15.0", - "pylint>=3.3.6", "ruff>=0.11.6", ] @@ -113,9 +111,8 @@ dependency_groups = [ "lint", ] commands = [ - ["flake8", "geoip2"], ["mypy", "geoip2", "tests"], - ["pylint", "geoip2"], + ["ruff", "check"], ["ruff", "format", "--check", "--diff", "."], ] diff --git a/uv.lock b/uv.lock index 5636ccc5..4c7e4b4e 100644 --- a/uv.lock +++ b/uv.lock @@ -126,18 +126,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, ] -[[package]] -name = "astroid" -version = "3.3.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731, upload-time = "2025-03-09T11:54:36.388Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339, upload-time = "2025-03-09T11:54:34.489Z" }, -] - [[package]] name = "async-timeout" version = "5.0.1" @@ -248,15 +236,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -266,20 +245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] -[[package]] -name = "flake8" -version = "7.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, -] - [[package]] name = "frozenlist" version = "1.6.0" @@ -408,9 +373,7 @@ dev = [ { name = "types-requests" }, ] lint = [ - { name = "flake8" }, { name = "mypy" }, - { name = "pylint" }, { name = "ruff" }, ] @@ -428,9 +391,7 @@ dev = [ { name = "types-requests", specifier = ">=2.32.0.20250328" }, ] lint = [ - { name = "flake8", specifier = ">=7.2.0" }, { name = "mypy", specifier = ">=1.15.0" }, - { name = "pylint", specifier = ">=3.3.6" }, { name = "ruff", specifier = ">=0.11.6" }, ] @@ -452,15 +413,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, -] - [[package]] name = "markupsafe" version = "3.0.2" @@ -625,15 +577,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/ab/6677aed7f7b1132ff1fbc2a499d75d1c7243b34839973df61384ac550991/maxminddb-2.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:15b178a5e1af71d7ea8e5374b3ae92d4ccbe52998dc57a737793d6dd029ec97c", size = 36647, upload-time = "2025-05-05T19:31:42.275Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "multidict" version = "6.4.3" @@ -810,15 +753,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] -[[package]] -name = "platformdirs" -version = "4.3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, -] - [[package]] name = "pluggy" version = "1.5.0" @@ -933,44 +867,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, -] - -[[package]] -name = "pyflakes" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, -] - -[[package]] -name = "pylint" -version = "3.3.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tomlkit" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586, upload-time = "2025-03-20T11:25:38.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462, upload-time = "2025-03-20T11:25:36.13Z" }, -] - [[package]] name = "pytest" version = "8.3.5" @@ -1079,15 +975,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "tomlkit" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload-time = "2024-08-14T08:19:41.488Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload-time = "2024-08-14T08:19:40.05Z" }, -] - [[package]] name = "types-requests" version = "2.32.0.20250328" From 57c955892fab8fb810726c7ce65c6de8b44da612 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 15:22:17 -0700 Subject: [PATCH 09/11] Remove pylint and flakes8 config --- .gitignore | 4 +--- geoip2/__init__.py | 2 -- geoip2/_internal.py | 3 --- geoip2/models.py | 3 --- geoip2/records.py | 6 ------ geoip2/webservice.py | 7 +++---- pyproject.toml | 3 --- setup.cfg | 3 --- 8 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore index ebd19299..c1ac9a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,8 @@ geoip2.egg-info/ MANIFEST .mypy_cache/ *.pyc -pylint.txt .pyre .pytype *.swp .tox -violations.pyflakes.txt -/venv \ No newline at end of file +/venv diff --git a/geoip2/__init__.py b/geoip2/__init__.py index 0df20a8b..8b92c9b9 100644 --- a/geoip2/__init__.py +++ b/geoip2/__init__.py @@ -1,7 +1,5 @@ """geoip2 client library.""" -# pylint:disable=C0111 - __title__ = "geoip2" __version__ = "5.1.0" __author__ = "Gregory Oschwald" diff --git a/geoip2/_internal.py b/geoip2/_internal.py index dd6fb65c..fc083b77 100644 --- a/geoip2/_internal.py +++ b/geoip2/_internal.py @@ -1,6 +1,5 @@ """Internal utilities.""" -# pylint: disable=too-few-public-methods from abc import ABCMeta @@ -13,7 +12,6 @@ def __eq__(self, other: object) -> bool: def __ne__(self, other: object) -> bool: return not self.__eq__(other) - # pylint: disable=too-many-branches def to_dict(self) -> dict: # noqa: C901, PLR0912 """Return a dict of the object suitable for serialization.""" result = {} @@ -42,7 +40,6 @@ def to_dict(self) -> dict: # noqa: C901, PLR0912 result[key] = value # network and ip_address are properties for performance reasons - # pylint: disable=no-member if hasattr(self, "ip_address") and self.ip_address is not None: result["ip_address"] = str(self.ip_address) if hasattr(self, "network") and self.network is not None: diff --git a/geoip2/models.py b/geoip2/models.py index 18526c8d..b41280b8 100644 --- a/geoip2/models.py +++ b/geoip2/models.py @@ -7,7 +7,6 @@ from __future__ import annotations -# pylint: disable=too-many-instance-attributes,too-few-public-methods,too-many-arguments import datetime import ipaddress from abc import ABCMeta @@ -335,7 +334,6 @@ class ASN(SimpleModel): for the IP address. """ - # pylint:disable=too-many-arguments,too-many-positional-arguments def __init__( self, ip_address: IPAddress, @@ -419,7 +417,6 @@ class ISP(ASN): organization: str | None """The name of the organization associated with the IP address.""" - # pylint:disable=too-many-arguments,too-many-positional-arguments def __init__( self, ip_address: IPAddress, diff --git a/geoip2/records.py b/geoip2/records.py index 9b2b15b0..1405b690 100644 --- a/geoip2/records.py +++ b/geoip2/records.py @@ -1,12 +1,8 @@ """Record classes used within the response models.""" -# pylint:disable=too-many-arguments,too-many-positional-arguments,too-many-instance-attributes,too-many-locals - from __future__ import annotations import ipaddress - -# pylint:disable=R0903 from abc import ABCMeta from ipaddress import IPv4Address, IPv6Address from typing import TYPE_CHECKING @@ -51,7 +47,6 @@ def __init__( @property def name(self) -> str | None: """The name based on the locales list passed to the constructor.""" - # pylint:disable=E1101 return next((self.names.get(x) for x in self._locales if x in self.names), None) @@ -175,7 +170,6 @@ def __init__( is_in_european_union: bool = False, iso_code: str | None = None, names: dict[str, str] | None = None, - # pylint:disable=redefined-builtin type: str | None = None, # noqa: A002 **_, ) -> None: diff --git a/geoip2/webservice.py b/geoip2/webservice.py index 37d407e9..0316116c 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -61,7 +61,7 @@ ) -class BaseClient: # pylint: disable=missing-class-docstring, too-few-public-methods +class BaseClient: """Base class for AsyncClient and Client.""" _account_id: str @@ -79,7 +79,6 @@ def __init__( timeout: float, ) -> None: """Construct a Client.""" - # pylint: disable=too-many-arguments,too-many-positional-arguments if locales is None: locales = ["en"] @@ -285,7 +284,7 @@ class AsyncClient(BaseClient): _existing_session: aiohttp.ClientSession _proxy: str | None - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments # noqa: PLR0913 + def __init__( # noqa: PLR0913 self, account_id: int, license_key: str, @@ -455,7 +454,7 @@ class Client(BaseClient): _session: requests.Session _proxies: dict[str, str] | None - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments # noqa: PLR0913 + def __init__( # noqa: PLR0913 self, account_id: int, license_key: str, diff --git a/pyproject.toml b/pyproject.toml index 8784b26b..4dcab4b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,9 +53,6 @@ Documentation = "https://geoip2.readthedocs.org/" "Source Code" = "https://github.com/maxmind/GeoIP2-python" "Issue Tracker" = "https://github.com/maxmind/GeoIP2-python/issues" -[tool.pylint."MESSAGES CONTROL"] -disable = "duplicate-code" - [tool.ruff.lint] select = ["ALL"] ignore = [ diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a8d46884..00000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -# black uses 88 : ¯\_(ツ)_/¯ -max-line-length = 88 From 726bcf91dcf0e4372e3a4799ad3fa5bd6db05f89 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 20 May 2025 15:46:12 -0700 Subject: [PATCH 10/11] Update ruff linting config --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4dcab4b6..622746de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,8 +62,11 @@ ignore = [ # documenting magic methods "D105", - # Don't bother with future imports for type annotations - "FA100", + # Conflicts with D211 + "D203", + + # Conflicts with D212 + "D213", # Magic numbers for HTTP status codes seem ok most of the time. "PLR2004", From 18be8ebda0bc8c8e45a21f24cce9e5103be711b0 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Wed, 21 May 2025 14:57:13 -0700 Subject: [PATCH 11/11] Disable redundant rule --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 622746de..0961762e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,9 @@ ignore = [ # Skip type annotation on **_ "ANN003", + # Redundant as the formatter handles missing trailing commas. + "COM812", + # documenting magic methods "D105",