From 49f5f0514903fef9c5e4ed62ee989ae51b8f637c Mon Sep 17 00:00:00 2001 From: Laurent Pellegrino Date: Mon, 10 Jun 2024 18:31:54 +0200 Subject: [PATCH] Introduce model for JSON responses --- CHANGELOG.md | 3 +- ipregistry/__init__.py | 1 + ipregistry/json.py | 274 +++++++++++++++++++++++++++++++++++++++++ ipregistry/model.py | 24 +--- ipregistry/request.py | 4 +- poetry.lock | 137 ++++++++++++++++++++- pyproject.toml | 7 +- tests/test_client.py | 5 +- 8 files changed, 427 insertions(+), 28 deletions(-) create mode 100644 ipregistry/json.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c56ab8..989c0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed -- Rename all function and variable names to use snake case. +- Introduce a data model for responses to enable field value access using dot notation and ensure code autocompletion functionality. +- Rename all function and variable names to adhere to snake_case convention. - Rename _IpregistryConfig_ option `apiUrl` to `baseUrl`. ## [3.2.0] - 2021-11-09 diff --git a/ipregistry/__init__.py b/ipregistry/__init__.py index 6caa115..7d08072 100644 --- a/ipregistry/__init__.py +++ b/ipregistry/__init__.py @@ -4,6 +4,7 @@ from .cache import * from .core import * +from .json import * from .model import * from .request import * from .util import * diff --git a/ipregistry/json.py b/ipregistry/json.py new file mode 100644 index 0000000..6afe94e --- /dev/null +++ b/ipregistry/json.py @@ -0,0 +1,274 @@ +from enum import Enum +from pydantic import BaseModel, ConfigDict +from typing import List, Optional, Union + + +class AsType(str, Enum): + BUSINESS = 'business' + EDUCATION = 'education' + GOVERNMENT = 'government' + HOSTING = 'hosting' + INACTIVE = 'inactive' + ISP = 'isp' + + +class RegionalInternetRegistry(str, Enum): + AFRINIC = 'AFRINIC' + APNIC = 'APNIC' + ARIN = 'ARIN' + JPNIC = 'JPNIC' + KRNIC = 'KRNIC' + LACNIC = 'LACNIC' + RIPE_NCC = 'RIPE_NCC' + TWNIC = 'TWNIC' + + +class AutonomousSystemPrefix(BaseModel): + cidr: Optional[str] = None + country_code: Optional[str] = None + network_name: Optional[str] = None + organization: Optional[str] = None + prefix: Optional[str] = None + registry: Optional[RegionalInternetRegistry] = None + size: Optional[int] = None + status: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class AutonomousSystemPrefixes(BaseModel): + ipv4_count: Optional[int] = None + ipv6_count: Optional[int] = None + ipv4: Optional[List[AutonomousSystemPrefix]] = [] + ipv6: Optional[List[AutonomousSystemPrefix]] = [] + + model_config = ConfigDict(extra='ignore') + + +class AutonomousSystemRelationships(BaseModel): + downstreams: Optional[List[int]] = [] + peers: Optional[List[int]] = [] + upstreams: Optional[List[int]] = [] + + model_config = ConfigDict(extra='ignore') + + +class AutonomousSystem(BaseModel): + allocated: Optional[str] = None + asn: Optional[int] = None + country_code: Optional[str] = None + domain: Optional[str] = None + name: Optional[str] = None + prefixes: Optional[AutonomousSystemPrefixes] = None + relationships: Optional[AutonomousSystemRelationships] = None + registry: Optional[RegionalInternetRegistry] = None + type: Optional[AsType] = None + updated: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Carrier(BaseModel): + name: Optional[str] = None + mcc: Optional[str] = None + mnc: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Company(BaseModel): + domain: Optional[str] = None + name: Optional[str] = None + type: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Connection(BaseModel): + asn: Optional[int] + domain: Optional[str] + organization: Optional[str] + route: Optional[str] + type: Optional[str] + + model_config = ConfigDict(extra='ignore') + + +class CurrencyFormatPrefixSuffix(BaseModel): + prefix: Optional[str] = None + suffix: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class CurrencyFormat(BaseModel): + negative: CurrencyFormatPrefixSuffix + positive: CurrencyFormatPrefixSuffix + + model_config = ConfigDict(extra='ignore') + + +class Currency(BaseModel): + code: Optional[str] = None + name: Optional[str] = None + name_native: Optional[str] = None + plural: Optional[str] = None + plural_native: Optional[str] = None + symbol: Optional[str] = None + symbol_native: Optional[str] = None + format: Optional[CurrencyFormat] = None + + model_config = ConfigDict(extra='ignore') + + +class Continent(BaseModel): + code: Optional[str] = None + name: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Flag(BaseModel): + emoji: Optional[str] = None + emoji_unicode: Optional[str] = None + emojitwo: Optional[str] = None + noto: Optional[str] = None + twemoji: Optional[str] = None + wikimedia: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Language(BaseModel): + code: Optional[str] = None + name: Optional[str] = None + native: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Country(BaseModel): + area: Optional[int] = None + borders: Optional[List[str]] = [] + calling_code: Optional[str] = None + capital: Optional[str] = None + code: Optional[str] = None + name: Optional[str] = None + population: Optional[int] = None + population_density: Optional[float] = None + flag: Optional[Flag] = None + languages: Optional[List[Language]] = [] + tld: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Region(BaseModel): + code: Optional[str] = None + name: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class Location(BaseModel): + continent: Optional[Continent] = None + country: Optional[Country] = None + region: Optional[Region] = None + city: Optional[str] = None + postal: Optional[str] = None + latitude: Optional[float] = None + longitude: Optional[float] = None + language: Optional[Language] = None + in_eu: Optional[bool] = None + + model_config = ConfigDict(extra='ignore') + + +class Security(BaseModel): + is_abuser: Optional[bool] = None + is_attacker: Optional[bool] = None + is_bogon: Optional[bool] = None + is_cloud_provider: Optional[bool] = None + is_proxy: Optional[bool] = None + is_relay: Optional[bool] = None + is_tor: Optional[bool] = None + is_tor_exit: Optional[bool] = None + is_anonymous: Optional[bool] = None + is_threat: Optional[bool] = None + is_vpn: Optional[bool] = None + + model_config = ConfigDict(extra='ignore') + + +class TimeZone(BaseModel): + id: Optional[str] = None + abbreviation: Optional[str] = None + current_time: Optional[str] = None + name: Optional[str] = None + offset: Optional[int] = None + in_daylight_saving: Optional[bool] = None + + model_config = ConfigDict(extra='ignore') + + +class UserAgentDevice(BaseModel): + brand: Optional[str] = None + name: Optional[str] = None + type: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class UserAgentEngine(BaseModel): + name: Optional[str] = None + type: Optional[str] = None + version: Optional[str] = None + version_major: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class UserAgentOperatingSystem(BaseModel): + name: Optional[str] = None + type: Optional[str] = None + version: Optional[str] = None + + model_config = ConfigDict(extra='ignore') + + +class UserAgent(BaseModel): + header: Optional[str] = None + name: Optional[str] = None + type: Optional[str] = None + version: Optional[str] = None + version_major: Optional[str] = None + device: UserAgentDevice = None + engine: UserAgentEngine = None + os: UserAgentOperatingSystem = None + + model_config = ConfigDict(extra='ignore') + + +class IpInfo(BaseModel): + ip: Optional[str] = None + type: Optional[str] = None + hostname: Optional[str] = None + carrier: Optional[Carrier] = None + company: Optional[Company] = None + connection: Optional[Connection] = None + currency: Optional[Currency] = None + location: Optional[Location] = None + security: Optional[Security] = None + time_zone: Optional[TimeZone] = None + + model_config = ConfigDict(extra='ignore') + + +class RequesterAutonomousSystem(AutonomousSystem): + pass + + +class RequesterIpInfo(IpInfo): + user_agent: Optional[UserAgent] = None + + model_config = ConfigDict(extra='ignore') \ No newline at end of file diff --git a/ipregistry/model.py b/ipregistry/model.py index 7c9b7f3..8e27eb1 100644 --- a/ipregistry/model.py +++ b/ipregistry/model.py @@ -14,32 +14,18 @@ limitations under the License. """ -import json +from .json import * -class JsonPayload: - def __init__(self, json): - self._json = json - - def __getattr__(self, attr): - return self._json[attr] - - def __repr__(self): - return self.__str__() - - def __str__(self): - return json.dumps(self._json, indent=4) - - -class IpInfo(JsonPayload): - pass +class RequesterIpInfo(IpInfo): + user_agent: UserAgent class IpregistryError(Exception): pass -class ApiError(JsonPayload, IpregistryError): +class ApiError(IpregistryError): pass @@ -47,5 +33,5 @@ class ClientError(IpregistryError): pass -class LookupError(JsonPayload, IpregistryError): +class LookupError(IpregistryError): pass diff --git a/ipregistry/request.py b/ipregistry/request.py index 2244cdc..df0fd19 100644 --- a/ipregistry/request.py +++ b/ipregistry/request.py @@ -59,7 +59,7 @@ def batch_lookup(self, ips, options): try: r = requests.post(self._build_base_url('', options), data=json.dumps(ips), headers=self._headers(), timeout=self._config.timeout) r.raise_for_status() - return list(map(lambda data: LookupError(data) if 'code' in data else IpInfo(data), r.json()['results'])) + return list(map(lambda data: LookupError(data) if 'code' in data else IpInfo(**data), r.json()['results'])) except requests.HTTPError: raise ApiError(r.json()) except Exception as e: @@ -72,7 +72,7 @@ def single_lookup(self, ip, options): try: r = requests.get(self._build_base_url(ip, options), headers=self._headers(), timeout=self._config.timeout) r.raise_for_status() - return IpInfo(r.json()) + return IpInfo(**r.json()) except requests.HTTPError: raise ApiError(r.json()) except Exception as e: diff --git a/poetry.lock b/poetry.lock index 2f30913..b1843ed 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,19 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "cachetools" version = "5.3.3" @@ -194,6 +208,116 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pydantic" +version = "2.7.3" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, + {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.4" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pytest" version = "8.2.2" @@ -259,6 +383,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + [[package]] name = "urllib3" version = "2.2.1" @@ -279,4 +414,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "75e3d4c0c60f28c739c270fe37f05ba4c08954db959fbccf455786c5c783f352" +content-hash = "00bd829248cd3870ae800f994339f2ca1c681c0ab8eba620500e7219bae34baf" diff --git a/pyproject.toml b/pyproject.toml index 147bb8d..e7d0c93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,10 +21,11 @@ repository = 'https://github.com/ipregistry/ipregistry-python' version = '3.2.0' [tool.poetry.dependencies] -cachetools = '>=4.2.4' +cachetools = '^4.2.4' +pydantic = "^2.7.3" python = '>=3.8' -requests = '>=2.26.0' -six = '>=1.16.0' +requests = '^2.26.0' +six = '^1.16.0' [tool.poetry.dev-dependencies] pytest = '>=6.2.5' diff --git a/tests/test_client.py b/tests/test_client.py index e0082ac..c784644 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -36,7 +36,8 @@ def test_simple_lookup(self): client = IpregistryClient(os.getenv('IPREGISTRY_API_KEY')) response = client.lookup('8.8.8.8') self.assertIsNotNone(response.ip) - self.assertIsNotNone(response.company['domain']) + self.assertIsNotNone(response.company.domain) + self.assertEqual('US', response.location.country.code) def test_simple_lookup_in_memory_cache(self): """ @@ -46,7 +47,7 @@ def test_simple_lookup_in_memory_cache(self): response = client.lookup('8.8.8.8') response = client.lookup('8.8.8.8') self.assertIsNotNone(response.ip) - self.assertIsNotNone(response.company['domain']) + self.assertIsNotNone(response.company.domain) if __name__ == '__main__':