From 5119d271fb6129bc71296fab7f1fc92a135cf533 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 25 Mar 2024 15:49:57 +0000 Subject: [PATCH 01/28] fixes lost emails due to early exit on org entity match --- rdap/objects.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rdap/objects.py b/rdap/objects.py index a34c8de..91b25dc 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -100,6 +100,7 @@ def _parse(self): emails = set() org_name = "" org_address = "" + org_name_final = False for ent in self._data.get("entities", []): vcard = self._parse_vcard(ent) @@ -113,7 +114,7 @@ def _parse(self): handle_url = self._rdapc.get_entity_url(handle) if "registrant" in roles: - if "fn" in vcard: + if "fn" in vcard and not org_name_final: org_name = vcard["fn"] if "adr" in vcard: org_address = vcard["adr"] @@ -135,8 +136,8 @@ def _parse(self): except RdapHTTPError: if not self._rdapc.config.get("ignore_recurse_errors"): raise - if "org" in kind: - break + if "org" in kind and org_name: + org_name_final = True # WORKAROUND APNIC keeps org info in remarks if "apnic" in self._data.get("port43", ""): From 11268fc4a346f50367522a797068ea6808079c72 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 25 Mar 2024 16:05:14 +0000 Subject: [PATCH 02/28] org_name_final moved into conditional --- rdap/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdap/objects.py b/rdap/objects.py index 91b25dc..94a05a2 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -116,6 +116,8 @@ def _parse(self): if "registrant" in roles: if "fn" in vcard and not org_name_final: org_name = vcard["fn"] + if "org" in kind: + org_name_final = True if "adr" in vcard: org_address = vcard["adr"] @@ -136,8 +138,6 @@ def _parse(self): except RdapHTTPError: if not self._rdapc.config.get("ignore_recurse_errors"): raise - if "org" in kind and org_name: - org_name_final = True # WORKAROUND APNIC keeps org info in remarks if "apnic" in self._data.get("port43", ""): From a4486d25225c4f259d07faec1cd88e632288c74d Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 25 Mar 2024 16:27:44 +0000 Subject: [PATCH 03/28] changelog --- CHANGELOG.md | 2 ++ CHANGELOG.yaml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c59b8eb..46748a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### Fixed +- issue introduced in 1.5.1 that could cause some contact points to be lost ## 1.5.1 diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index a073510..a31d6db 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,6 +1,7 @@ Unreleased: added: [] - fixed: [] + fixed: + - issue introduced in 1.5.1 that could cause some contact points to be lost changed: [] deprecated: [] removed: [] From b14010d2f13606efb334dd4e7e5fe5c2c7a77407 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 25 Mar 2024 16:31:50 +0000 Subject: [PATCH 04/28] handle org address the same way --- rdap/objects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rdap/objects.py b/rdap/objects.py index 94a05a2..e197021 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -101,6 +101,7 @@ def _parse(self): org_name = "" org_address = "" org_name_final = False + org_address_final = False for ent in self._data.get("entities", []): vcard = self._parse_vcard(ent) @@ -118,8 +119,10 @@ def _parse(self): org_name = vcard["fn"] if "org" in kind: org_name_final = True - if "adr" in vcard: + if "adr" in vcard and not org_address_final: org_address = vcard["adr"] + if "org" in kind: + org_address_final = True # check nested entities for nent in ent.get("entities", []): From b926b873bcc83edd8503508020f7d4976447edc4 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 25 Mar 2024 17:49:19 +0000 Subject: [PATCH 05/28] fix test data --- tests/data/rdap/autnum/205697.expected | 1 + tests/data/rdap/autnum/205726.expected | 1 + tests/data/rdap/autnum/206050.expected | 1 + tests/data/rdap/autnum/2914.expected | 1 + tests/data/rdap/autnum/37271.expected | 3 +- tests/data/rdap/autnum/49037.expected | 4 +- tests/data/rdap/autnum/8283.expected | 8 +- tests/data/rdap/autnum/9269.expected | 1 + tests/data/rdap/entity/BRI2.input | 1 + tests/data/rdap/entity/SD12478-RIPE.input | 67 ++++++++++++++++ tests/data/rdap/entity/WA2477-RIPE.input | 77 +++++++++++++++++++ .../data/rdap/rdap/entity/PP17-AFRINIC.input | 1 + tests/data/rdap/rdap/entity/WOL-AFRINIC.input | 1 + 13 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 tests/data/rdap/entity/BRI2.input create mode 100644 tests/data/rdap/entity/SD12478-RIPE.input create mode 100644 tests/data/rdap/entity/WA2477-RIPE.input create mode 100644 tests/data/rdap/rdap/entity/PP17-AFRINIC.input create mode 100644 tests/data/rdap/rdap/entity/WOL-AFRINIC.input diff --git a/tests/data/rdap/autnum/205697.expected b/tests/data/rdap/autnum/205697.expected index 3160db8..747278b 100644 --- a/tests/data/rdap/autnum/205697.expected +++ b/tests/data/rdap/autnum/205697.expected @@ -1,6 +1,7 @@ { "name": "ALOJALIA", "emails": [ + "abuse@alojalia.com", "alejandro@alojalia.com", "angel@alojalia.com" ], diff --git a/tests/data/rdap/autnum/205726.expected b/tests/data/rdap/autnum/205726.expected index 144e342..2eacf59 100644 --- a/tests/data/rdap/autnum/205726.expected +++ b/tests/data/rdap/autnum/205726.expected @@ -1,6 +1,7 @@ { "name": "Vusam", "emails": [ + "abuse@vusam.com", "daan.vangorkum@vusam.com", "support@vusam.com" ], diff --git a/tests/data/rdap/autnum/206050.expected b/tests/data/rdap/autnum/206050.expected index 1f74cc1..ae066b8 100644 --- a/tests/data/rdap/autnum/206050.expected +++ b/tests/data/rdap/autnum/206050.expected @@ -1,6 +1,7 @@ { "name": "elitnet-tr", "emails": [ + "abuse@geoipa.com", "mehmet@elitnet.net.tr" ], "org_name": "ELITNET TELEKOMUNIKASYON INTERNET VE ILETISIM HIZ.TIC LTD.STI.", diff --git a/tests/data/rdap/autnum/2914.expected b/tests/data/rdap/autnum/2914.expected index c6921ce..1df7aef 100644 --- a/tests/data/rdap/autnum/2914.expected +++ b/tests/data/rdap/autnum/2914.expected @@ -4,6 +4,7 @@ "abuse@ntt.net", "ipr@gin.ntt.net", "massimo@ntt.net", + "peering@ntt.net", "support@us.ntt.net" ], "org_name": "NTT America, Inc.", diff --git a/tests/data/rdap/autnum/37271.expected b/tests/data/rdap/autnum/37271.expected index b2128cf..9f8fa5e 100644 --- a/tests/data/rdap/autnum/37271.expected +++ b/tests/data/rdap/autnum/37271.expected @@ -3,7 +3,8 @@ "emails": [ "abuse@workonline.africa", "communications@workonline.africa", - "noc@workonline.africa" + "noc@workonline.africa", + "peterp@workonline.africa" ], "org_name": "Workonline Communications(Pty) Ltd", "org_address": "114 West St\nJohannesburg 2196" diff --git a/tests/data/rdap/autnum/49037.expected b/tests/data/rdap/autnum/49037.expected index 54aadbd..a4cefe2 100644 --- a/tests/data/rdap/autnum/49037.expected +++ b/tests/data/rdap/autnum/49037.expected @@ -1,7 +1,9 @@ { "name": "PG19", "emails": [ - "mikhail.majorov@gmail.com" + "abuse@webrocket.am", + "mikhail.majorov@gmail.com", + "ripe@webrocket.am" ], "org_name": "PROSTIE RESHENIA LLC, ****************** Routing Policy *******************", "org_address": "ST. MOVSESA HORENACI, 14/20\n1109\nVAGARSHAPAT\nARMENIA" diff --git a/tests/data/rdap/autnum/8283.expected b/tests/data/rdap/autnum/8283.expected index c42f5ff..97d22ee 100644 --- a/tests/data/rdap/autnum/8283.expected +++ b/tests/data/rdap/autnum/8283.expected @@ -2,8 +2,14 @@ "name": "COLOCLUE-AS", "emails": [ "abuse@coloclue.net", + "contact@cybertinus.nl", + "hostmaster@bytepark.net", + "hostmaster@luje.net", + "menno@someones.net", "ops@coloclue.net", - "routers@coloclue.net" + "ripe@jurrian.vaniersel.net", + "routers@coloclue.net", + "weerd@weirdnet.nl" ], "org_name": "Netwerkvereniging Coloclue, Netwerkvereniging Coloclue, Amsterdam, Netherlands", "org_address": "Frans Duwaerstraat 34\n1318 AC\nAlmere\nNETHERLANDS" diff --git a/tests/data/rdap/autnum/9269.expected b/tests/data/rdap/autnum/9269.expected index 54c4bab..d09476b 100644 --- a/tests/data/rdap/autnum/9269.expected +++ b/tests/data/rdap/autnum/9269.expected @@ -1,6 +1,7 @@ { "name": "HKBN-AS-AP", "emails": [ + "abuse@hkbn.com.hk", "abuse@hkbn.net", "hostmaster@hkbn.com.hk", "nocsn@hkbn.com.hk" diff --git a/tests/data/rdap/entity/BRI2.input b/tests/data/rdap/entity/BRI2.input new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/tests/data/rdap/entity/BRI2.input @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/data/rdap/entity/SD12478-RIPE.input b/tests/data/rdap/entity/SD12478-RIPE.input new file mode 100644 index 0000000..f780050 --- /dev/null +++ b/tests/data/rdap/entity/SD12478-RIPE.input @@ -0,0 +1,67 @@ +{ + "handle" : "SD12478-RIPE", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "Yavuz Selim MALKOC" ], [ "kind", { }, "text", "individual" ], [ "adr", { + "label" : "Sultan Orhan Mah. Ilyasbey Cad, 1812 Sk.\n41400\nKocaeli\nTURKEY" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "tel", { + "type" : "voice" + }, "text", "+902626446663" ] ] ], + "entities" : [ { + "handle" : "mnt-tr-internetten-1", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "mnt-tr-internetten-1" ], [ "kind", { }, "text", "individual" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/SD12478-RIPE", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/mnt-tr-internetten-1" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "objectClassName" : "entity" + } ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/SD12478-RIPE", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/SD12478-RIPE" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "events" : [ { + "eventAction" : "registration", + "eventDate" : "2019-09-27T12:14:59Z" + }, { + "eventAction" : "last changed", + "eventDate" : "2019-09-30T13:41:16Z" + } ], + "rdapConformance" : [ "cidr0", "rdap_level_0", "nro_rdap_profile_0", "redacted" ], + "notices" : [ { + "title" : "Filtered", + "description" : [ "This output has been filtered." ] + }, { + "title" : "Whois Inaccuracy Reporting", + "description" : [ "If you see inaccuracies in the results, please visit:" ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/SD12478-RIPE", + "rel" : "inaccuracy-report", + "href" : "https://www.ripe.net/contact-form?topic=ripe_dbm&show_form=true", + "type" : "text/html" + } ] + }, { + "title" : "Source", + "description" : [ "Objects returned came from source", "RIPE" ] + }, { + "title" : "Terms and Conditions", + "description" : [ "This is the RIPE Database query service. The objects are in RDAP format." ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/SD12478-RIPE", + "rel" : "terms-of-service", + "href" : "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "type" : "application/pdf" + } ] + } ], + "port43" : "whois.ripe.net", + "objectClassName" : "entity" +} \ No newline at end of file diff --git a/tests/data/rdap/entity/WA2477-RIPE.input b/tests/data/rdap/entity/WA2477-RIPE.input new file mode 100644 index 0000000..b674a26 --- /dev/null +++ b/tests/data/rdap/entity/WA2477-RIPE.input @@ -0,0 +1,77 @@ +{ + "handle" : "WA2477-RIPE", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "WEBROCKET SUPPORT TEAM" ], [ "kind", { }, "text", "group" ], [ "adr", { + "label" : "st. Movsesa Horenaci 14/20, Vagharshapat, Armavir, Armenia, 1101" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "email", { + "type" : "abuse" + }, "text", "abuse@webrocket.am" ] ] ], + "entities" : [ { + "handle" : "WEBROCKET-MNT", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "WEBROCKET-MNT" ], [ "kind", { }, "text", "individual" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/WA2477-RIPE", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/WEBROCKET-MNT" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "objectClassName" : "entity" + } ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/WA2477-RIPE", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/WA2477-RIPE" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "events" : [ { + "eventAction" : "registration", + "eventDate" : "2023-07-01T14:23:57Z" + }, { + "eventAction" : "last changed", + "eventDate" : "2023-07-01T14:33:34Z" + } ], + "rdapConformance" : [ "cidr0", "rdap_level_0", "nro_rdap_profile_0", "redacted" ], + "notices" : [ { + "title" : "Filtered", + "description" : [ "This output has been filtered." ] + }, { + "title" : "Whois Inaccuracy Reporting", + "description" : [ "If you see inaccuracies in the results, please visit:" ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/WA2477-RIPE", + "rel" : "inaccuracy-report", + "href" : "https://www.ripe.net/contact-form?topic=ripe_dbm&show_form=true", + "type" : "text/html" + } ] + }, { + "title" : "Source", + "description" : [ "Objects returned came from source", "RIPE" ] + }, { + "title" : "Terms and Conditions", + "description" : [ "This is the RIPE Database query service. The objects are in RDAP format." ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/WA2477-RIPE", + "rel" : "terms-of-service", + "href" : "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "type" : "application/pdf" + } ] + } ], + "port43" : "whois.ripe.net", + "objectClassName" : "entity", + "redacted" : [ { + "name" : { + "description" : "Personal e-mail information" + }, + "reason" : { + "description" : "Personal data" + }, + "prePath" : "$.vcardArray[1][?(@[0]=='e-mail')]", + "method" : "removal" + } ] +} \ No newline at end of file diff --git a/tests/data/rdap/rdap/entity/PP17-AFRINIC.input b/tests/data/rdap/rdap/entity/PP17-AFRINIC.input new file mode 100644 index 0000000..97a53a3 --- /dev/null +++ b/tests/data/rdap/rdap/entity/PP17-AFRINIC.input @@ -0,0 +1 @@ +{"rdapConformance":["rdap_level_0","nro_rdap_profile_0"],"notices":[{"title":"ABOUT","description":["This is the AfriNIC RDAP server."],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"describedby","href":"https://www.afrinic.net/support/whois-db/reference-manual","hreflang":["en"],"type":"text/html","title":"AFRINIC Database Reference Manual","media":"screen"}]},{"title":"Terms and Conditions","description":["This is the AFRINIC Database query service. The objects are in RDAP format."],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"terms-of-service","href":"https://afrinic.net/whois/terms","type":"text/html"}]},{"title":"Whois Inaccuracy Reporting","description":["If you see inaccuracies in the results, please visit: "],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"inaccuracy-report","href":"https://www.afrinic.net/support/whois-db/reference-manual","type":"text/html","title":"AFRINIC WHOIS Inaccuracy Report"}]},{"title":"jCard sunset end","description":["2023-12-31T23:59:59Z"],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"related","href":"https://www.ietf.org/id/draft-ietf-regext-rdap-jscontact-12.html","type":"text/html","title":"jCard deprecation and replacement with JSContact Card"},{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"alternate","href":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC?jscard=1","type":"application/rdap+json","title":"JSContact Card version"}]},{"title":"jCard deprecation end","description":["2022-12-31T23:59:59Z"],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"related","href":"https://www.ietf.org/id/draft-ietf-regext-rdap-jscontact-12.html","type":"text/html","title":"jCard deprecation and replacement with JSContact Card"},{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"alternate","href":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC?jscard=1","type":"application/rdap+json","title":"JSContact Card version"}]}],"lang":"en","links":[{"value":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC","rel":"self","href":"https://rdap.afrinic.net/rdap/entity/PP17-AFRINIC"}],"handle":"PP17-AFRINIC","status":["active"],"port43":"whois.afrinic.net","vcardArray":["vcard",[["version",{},"text","4.0"],["kind",{},"text","individual"],["fn",{},"text","Peter Peele"],["tel",{"type":"work"},"text","tel:+27-82-064-3322"],["email",{},"text","peterp@workonline.africa"],["adr",{"label":"114 West Street\nSandton\nSouth Africa\nPostal Code 2066"},"text",["114 West Street","Sandton","South Africa","Postal Code 2066","","",""]]]],"objectClassName":"entity"} \ No newline at end of file diff --git a/tests/data/rdap/rdap/entity/WOL-AFRINIC.input b/tests/data/rdap/rdap/entity/WOL-AFRINIC.input new file mode 100644 index 0000000..16b4940 --- /dev/null +++ b/tests/data/rdap/rdap/entity/WOL-AFRINIC.input @@ -0,0 +1 @@ +{"rdapConformance":["rdap_level_0","nro_rdap_profile_0"],"notices":[{"title":"ABOUT","description":["This is the AfriNIC RDAP server."],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"describedby","href":"https://www.afrinic.net/support/whois-db/reference-manual","hreflang":["en"],"type":"text/html","title":"AFRINIC Database Reference Manual","media":"screen"}]},{"title":"Terms and Conditions","description":["This is the AFRINIC Database query service. The objects are in RDAP format."],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"terms-of-service","href":"https://afrinic.net/whois/terms","type":"text/html"}]},{"title":"Whois Inaccuracy Reporting","description":["If you see inaccuracies in the results, please visit: "],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"inaccuracy-report","href":"https://www.afrinic.net/support/whois-db/reference-manual","type":"text/html","title":"AFRINIC WHOIS Inaccuracy Report"}]},{"title":"jCard sunset end","description":["2023-12-31T23:59:59Z"],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"related","href":"https://www.ietf.org/id/draft-ietf-regext-rdap-jscontact-12.html","type":"text/html","title":"jCard deprecation and replacement with JSContact Card"},{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"alternate","href":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC?jscard=1","type":"application/rdap+json","title":"JSContact Card version"}]},{"title":"jCard deprecation end","description":["2022-12-31T23:59:59Z"],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"related","href":"https://www.ietf.org/id/draft-ietf-regext-rdap-jscontact-12.html","type":"text/html","title":"jCard deprecation and replacement with JSContact Card"},{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"alternate","href":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC?jscard=1","type":"application/rdap+json","title":"JSContact Card version"}]}],"lang":"en","links":[{"value":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC","rel":"self","href":"https://rdap.afrinic.net/rdap/entity/WOL-AFRINIC"}],"handle":"WOL-AFRINIC","status":["active"],"port43":"whois.afrinic.net","vcardArray":["vcard",[["version",{},"text","4.0"],["kind",{},"text","group"],["fn",{},"text","Workonline NOC"],["tel",{"type":"work"},"text","tel:+27-21-200-9009"],["email",{},"text","noc@workonline.africa"],["adr",{"label":"114 West St\nJohannesburg 2196\nSouth Africa"},"text",["114 West St","Johannesburg 2196","South Africa","","","",""]]]],"entities":[{"lang":"en","remarks":[{"title":"Remark","description":["Ben Maddison"]}],"links":[{"value":"https://rdap.afrinic.net/rdap/entity/BM15-AFRINIC","rel":"self","href":"https://rdap.afrinic.net/rdap/entity/BM15-AFRINIC"}],"handle":"BM15-AFRINIC","status":["active"],"port43":"whois.afrinic.net","vcardArray":["vcard",[["version",{},"text","4.0"],["kind",{},"text","individual"],["fn",{},"text","Ben Maddison"],["tel",{"type":"work"},"text","tel:+27-21-200-9000"],["email",{},"text","benm@workonline.africa"],["adr",{"label":"114 West St\nJohannesburg 2916\nSouth Africa"},"text",["114 West St","Johannesburg 2916","South Africa","","","",""]]]],"roles":["administrative","technical"],"objectClassName":"entity"},{"lang":"en","links":[{"value":"https://rdap.afrinic.net/rdap/entity/ORG-WCL1-AFRINIC","rel":"self","href":"https://rdap.afrinic.net/rdap/entity/ORG-WCL1-AFRINIC"}],"handle":"ORG-WCL1-AFRINIC","status":["active"],"port43":"whois.afrinic.net","vcardArray":["vcard",[["version",{},"text","4.0"],["kind",{},"text","org"],["fn",{},"text","Workonline Communications(Pty) Ltd"],["tel",{"type":"work"},"text","tel:+27-21-200-9000"],["tel",{"type":"work"},"text","tel:+27-21-200-9009"],["email",{},"text","noc@workonline.africa"],["email",{},"text","abuse@workonline.africa"],["email",{},"text","communications@workonline.africa"],["adr",{"label":"114 West St\nJohannesburg 2196"},"text",["114 West St","Johannesburg 2196","","","","",""]]]],"roles":["organisation"],"objectClassName":"entity"}],"objectClassName":"entity"} \ No newline at end of file From dc418db815b3746b075e89155676e112a26151c0 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 4 Jul 2024 16:27:47 +0000 Subject: [PATCH 06/28] pydantic schemas for rdap normalization --- pyproject.toml | 2 +- rdap/schema/__init__.py | 0 rdap/schema/normalized.py | 151 ++++++++++++++++++++++++++++++++++++++ rdap/schema/source.py | 0 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 rdap/schema/__init__.py create mode 100644 rdap/schema/normalized.py create mode 100644 rdap/schema/source.py diff --git a/pyproject.toml b/pyproject.toml index f72e839..7a23a44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ rdap = "rdap.cli:main" python = "^3.8" requests = ">=2.25.1" munge = {version = ">=1.3", extras = ["yaml"]} - +pydantic = ">2.8.2" [tool.poetry.dev-dependencies] # testing diff --git a/rdap/schema/__init__.py b/rdap/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py new file mode 100644 index 0000000..bd51499 --- /dev/null +++ b/rdap/schema/normalized.py @@ -0,0 +1,151 @@ +""" +Pydantic schemas for normalized RDAP data +""" + +import datetime +import pydantic +import enum +import ipaddress + +__all__ = [ + "IP_VERSION", + "STATUS", + "ROLE", + "DNSSEC", + "GeoLocation", + "Location", + "Contact", + "Source", + "Organization", + "Network", + "IPNetwork", + "Entity", + "Nameserver", + "Domain" +] + +class IP_VERSION(int, enum.Enum): + """ + Enum for IP version + """ + ipv4 = 4 + ipv6 = 6 + +class STATUS(str, enum.Enum): + active = "active" + inactive = "inactive" + +class ROLE(str, enum.Enum): + abuse = "abuse" + admin = "admin" + policy = "policy" + technical = "technical" + +class DNSSEC(str, enum.Enum): + secure = "secure" + insecure = "insecure" + unknown = "unknown" + +class GeoLocation(pydantic.BaseModel): + """ + Describes geographic coordinates + """ + latitude: float + longitude: float + +class Location(pydantic.BaseModel): + """ + Describes a location + """ + updated: datetime + country: str + city: str + postal_code: str + address: str + geo: GeoLocation | None = None + floor: str | None = None + suite: str | None = None + +class Contact(pydantic.BaseModel): + """ + Describes a point of contact + """ + created: datetime + updated: datetime + name: str + roles: list[ROLE] = pydantic.Field(default_factory=list) + phone: str | None = None + email: str | None = None + +class Source(pydantic.BaseModel): + """ + Describes a source of rdap data + + Will contain where the data was fetched from and when + """ + created: datetime + updated: datetime + handle: str + urls: list[str] + description: str | None = None + + +class Organization(pydantic.BaseModel): + """ + Describes an organization + """ + name: str + +class Network(pydantic.BaseModel): + """ + Describes a network + """ + created: datetime + updated: datetime + asn: int + name: str + organization: Organization + location: Location + contacts: list[Contact] = pydantic.Field(default_factory=list) + sources: list[Source] = pydantic.Field(default_factory=list) + + +class IPNetwork(pydantic.BaseModel): + """ + Describes an IP network + """ + prefix: ipaddress.IPv4Network | ipaddress.IPv6Network + version: IP_VERSION + name: str + type: str + STATUS: STATUS + parent: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None + contacts: list[Contact] = pydantic.Field(default_factory=list) + sources: list[Source] = pydantic.Field(default_factory=list) + +class Entity(pydantic.BaseModel): + """ + Describes an entity + """ + name: str + organization: Organization + location: Location + contacts: list[Contact] = pydantic.Field(default_factory=list) + sources: list[Source] = pydantic.Field(default_factory=list) + +class Nameserver(pydantic.BaseModel): + """ + Describes a nameserver + """ + host: str + +class Domain(pydantic.BaseModel): + """ + Describes a domain + """ + name: str + handle: str + dns_sec: DNSSEC + nameservers: list[Nameserver] = pydantic.Field(default_factory=list) + contacts: list[Contact] = pydantic.Field(default_factory=list) + sources: list[Source] = pydantic.Field(default_factory=list) \ No newline at end of file diff --git a/rdap/schema/source.py b/rdap/schema/source.py new file mode 100644 index 0000000..e69de29 From 90f1386a4768a4456fb48f228f5899e53cb98852 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 4 Jul 2024 16:33:49 +0000 Subject: [PATCH 07/28] merge cruft --- CHANGELOG.md | 2 -- CHANGELOG.yaml | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bf482a..12029a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Unreleased -### Fixed -- issue introduced in 1.5.1 that could cause some contact points to be lost ## 1.5.2 diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index f9f571f..b49935b 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,7 +1,6 @@ Unreleased: added: [] - fixed: - - issue introduced in 1.5.1 that could cause some contact points to be lost + fixed: [] changed: [] deprecated: [] removed: [] From dca69e96abba52360b57f4a6f1d540485fba9699 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Tue, 9 Jul 2024 17:47:41 +0000 Subject: [PATCH 08/28] source schemas for arin --- poetry.lock | 689 +++++++++++++++++++++++++----------------- pyproject.toml | 2 +- rdap/objects.py | 6 + rdap/schema/arin.py | 185 ++++++++++++ rdap/schema/source.py | 25 ++ 5 files changed, 626 insertions(+), 281 deletions(-) create mode 100644 rdap/schema/arin.py diff --git a/poetry.lock b/poetry.lock index 062a500..e7e6734 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,35 +1,50 @@ # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +category = "main" +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 = "black" -version = "24.3.0" +version = "24.4.2" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [package.dependencies] @@ -61,14 +76,14 @@ files = [ [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -224,64 +239,64 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.4" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, + {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, + {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, + {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, + {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, + {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, + {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, + {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, + {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, + {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, + {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, + {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, + {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] [package.dependencies] @@ -292,14 +307,14 @@ toml = ["tomli"] [[package]] name = "decorator" -version = "4.4.2" +version = "5.1.1" description = "Decorators for Humans" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" +python-versions = ">=3.5" files = [ - {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, - {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] [[package]] @@ -316,14 +331,14 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -331,19 +346,19 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.13.1" +version = "3.15.4" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -363,17 +378,6 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" -[[package]] -name = "future" -version = "0.18.3" -description = "Clean single-source support for Python 3 and 2" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, -] - [[package]] name = "ghp-import" version = "2.1.0" @@ -394,14 +398,14 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "identify" -version = "2.5.35" +version = "2.6.0" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -409,35 +413,35 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.0.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, + {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -468,14 +472,14 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -617,35 +621,53 @@ files = [ [[package]] name = "mkdocs" -version = "1.5.3" +version = "1.6.0" description = "Project documentation with Markdown." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, - {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, + {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, + {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1" +markdown = ">=3.3.6" markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" packaging = ">=20.5" pathspec = ">=0.11.1" -platformdirs = ">=2.2.0" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" [[package]] name = "munge" @@ -671,39 +693,39 @@ yaml = ["PyYAML (>=5.1)"] [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.1" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -731,29 +753,26 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -770,30 +789,31 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -831,6 +851,131 @@ files = [ {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyflakes" version = "2.5.0" @@ -845,34 +990,34 @@ files = [ [[package]] name = "pyproject-api" -version = "1.6.1" +version = "1.7.1" description = "API to interact with the python pyproject.toml based projects" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"}, - {file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"}, + {file = "pyproject_api-1.7.1-py3-none-any.whl", hash = "sha256:2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb"}, + {file = "pyproject_api-1.7.1.tar.gz", hash = "sha256:7ebc6cd10710f89f4cf2a2731710a98abce37ebff19427116ff2174c9236a827"}, ] [package.dependencies] -packaging = ">=23.1" +packaging = ">=24.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [package.extras] -docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"] +docs = ["furo (>=2024.5.6)", "sphinx-autodoc-typehints (>=2.2.1)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=70.1)"] [[package]] name = "pytest" -version = "8.1.1" +version = "8.2.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -880,22 +1025,22 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.4,<2.0" +pluggy = ">=1.5,<2.0" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "5.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] @@ -903,7 +1048,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" @@ -926,19 +1071,19 @@ testing = ["Django", "django-configurations (>=2.0)"] [[package]] name = "pytest-filedata" -version = "0.4.0" -description = "easily load data from files" +version = "1.0.0" +description = "easily load test data from files" category = "dev" optional = false -python-versions = "*" +python-versions = "<4.0,>=3.8" files = [ - {file = "pytest_filedata-0.4.0.tar.gz", hash = "sha256:3a2a3f346087ac82dfd313212cd2d61c5fcfd23b0aecaa2484e6c31cfcb32fd5"}, + {file = "pytest_filedata-1.0.0-py3-none-any.whl", hash = "sha256:95d624047546eb96871c19f4b690d23c6920da942e59e97f3de24ad0b81400da"}, + {file = "pytest_filedata-1.0.0.tar.gz", hash = "sha256:b92e62832ee29797725961808590ca74c634dfdd5ae297ac0fed261cd3170d03"}, ] [package.dependencies] -decorator = ">=4,<5" -future = ">=0.16.0,<1" -requests-mock = ">=1,<2" +decorator = ">=4" +requests-mock = ">=1" [[package]] name = "python-dateutil" @@ -1047,14 +1192,14 @@ pyyaml = "*" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1069,40 +1214,21 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-mock" -version = "1.11.0" +version = "1.12.1" description = "Mock out responses from the requests package" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, - {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, + {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, + {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, ] [package.dependencies] -requests = ">=2.3,<3" -six = "*" +requests = ">=2.22,<3" [package.extras] fixture = ["fixtures"] -test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] - -[[package]] -name = "setuptools" -version = "69.2.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1142,31 +1268,31 @@ files = [ [[package]] name = "tox" -version = "4.14.1" +version = "4.16.0" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.14.1-py3-none-any.whl", hash = "sha256:b03754b6ee6dadc70f2611da82b4ed8f625fcafd247e15d1d0cb056f90a06d3b"}, - {file = "tox-4.14.1.tar.gz", hash = "sha256:f0ad758c3bbf7e237059c929d3595479363c3cdd5a06ac3e49d1dd020ffbee45"}, + {file = "tox-4.16.0-py3-none-any.whl", hash = "sha256:61e101061b977b46cf00093d4319438055290ad0009f84497a07bf2d2d7a06d0"}, + {file = "tox-4.16.0.tar.gz", hash = "sha256:43499656f9949edb681c0f907f86fbfee98677af9919d8b11ae5ad77cb800748"}, ] [package.dependencies] -cachetools = ">=5.3.2" +cachetools = ">=5.3.3" chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.13.1" -packaging = ">=23.2" -platformdirs = ">=4.1" -pluggy = ">=1.3" -pyproject-api = ">=1.6.1" +filelock = ">=3.15.4" +packaging = ">=24.1" +platformdirs = ">=4.2.2" +pluggy = ">=1.5" +pyproject-api = ">=1.7.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} -virtualenv = ">=20.25" +virtualenv = ">=20.26.3" [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.25.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] -testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=8.0.2)", "distlib (>=0.3.8)", "flaky (>=3.7)", "hatch-vcs (>=0.4)", "hatchling (>=1.21)", "psutil (>=5.9.7)", "pytest (>=7.4.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-xdist (>=3.5)", "re-assert (>=1.1)", "time-machine (>=2.13)", "wheel (>=0.42)"] +docs = ["furo (>=2024.5.6)", "sphinx (>=7.3.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] +testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.2)", "time-machine (>=2.14.2)", "wheel (>=0.43)"] [[package]] name = "tox-gh-actions" @@ -1188,26 +1314,26 @@ testing = ["black", "devpi-process", "flake8 (>=6,<7)", "mypy", "pytest (>=7,<8) [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "dev" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {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" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -1218,14 +1344,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.3" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, ] [package.dependencies] @@ -1234,46 +1360,49 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "watchdog" -version = "4.0.0" +version = "4.0.1" description = "Filesystem events monitoring" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, - {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, - {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, - {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, - {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, - {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, - {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, - {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, ] [package.extras] @@ -1281,21 +1410,21 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "zipp" -version = "3.18.1" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "745cf65e48c3128e1949b8330b392907847d63b691428513ecb00f39f52e5e06" +content-hash = "107cf60642ce375bceda9d23dff9f7b5957b30698122196d0f32a5f336e29f2d" diff --git a/pyproject.toml b/pyproject.toml index cff4601..75cd167 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ rdap = "rdap.cli:main" python = "^3.8" requests = ">=2.25.1" munge = {version = ">=1.3", extras = ["yaml"]} -pydantic = ">2.8.2" +pydantic = ">=2.8.2" [tool.poetry.dev-dependencies] # testing diff --git a/rdap/objects.py b/rdap/objects.py index e197021..2e30e43 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -1,5 +1,6 @@ from rdap.exceptions import RdapHTTPError, RdapNotFoundError +from rdap.schema.source import autnum_model def rir_from_domain(domain): """Gets the RIR from a URL or domain, if possible""" @@ -206,11 +207,16 @@ def __init__(self, data, rdapc=None): super().__init__(data, rdapc) + @property + def model(self): + return autnum_model(self.get_rir())(**self._data) + class RdapNetwork(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) + class RdapDomain(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) diff --git a/rdap/schema/arin.py b/rdap/schema/arin.py new file mode 100644 index 0000000..2211b03 --- /dev/null +++ b/rdap/schema/arin.py @@ -0,0 +1,185 @@ +from pydantic import BaseModel, Field +from datetime import datetime +from typing import Any + +class Link(BaseModel): + """Represents a hyperlink in the RDAP response.""" + # The label or description of the link + value: str + # The relationship of the link to the current object + rel: str + # The MIME type of the target resource + type: str + # The URL of the link + href: str + +class Event(BaseModel): + """Represents a timestamped event in the lifecycle of an RDAP object.""" + # The type of event (e.g., "registration", "last changed") + eventAction: str + # The date and time of the event + eventDate: datetime + +class Notice(BaseModel): + """Represents a notice or message in the RDAP response.""" + # The title of the notice + title: str + # A list of text lines comprising the notice + description: list[str] + # Optional links related to the notice + links: list[Link] | None = None + +class VCardValue(BaseModel): + """Represents additional properties for vCard values.""" + # Types associated with the vCard value (e.g., "work", "voice" for telephone) + type: list[str] | None = None + +class VCardEntry(BaseModel): + """Represents a single entry in a vCard.""" + # The name of the vCard property (e.g., "version", "fn", "adr") + name: str + # Metadata associated with the entry + meta: dict = Field(default_factory=dict) + # The value of the vCard property + value: str | list[str] | VCardValue + +class VCard(BaseModel): + """Represents a vCard (contact information) in the RDAP response.""" + version: VCardEntry + # Formatted Name + fn: VCardEntry + # Address + adr: VCardEntry | None = None + # Kind of object (e.g., individual, org, group) + kind: VCardEntry | None = None + email: VCardEntry | None = None + # Telephone + tel: VCardEntry | None = None + # Name components + n: VCardEntry | None = None + # Organization + org: VCardEntry | None = None + +class Remark(BaseModel): + """Represents a remark or comment in the RDAP response.""" + # The title of the remark + title: str + # A list of text lines comprising the remark + description: list[str] + +class Entity(BaseModel): + """Represents an entity (organization, individual, or role) in the RDAP response.""" + # A unique identifier for the entity + handle: str + # Contact information in vCard format + vcardArray: list[str | list[VCardEntry] | list[Any]] = Field(default_factory=list) + # Roles of the entity (e.g., registrant, technical, administrative) + roles: list[str] + # Links related to the entity + links: list[Link] + # Events associated with the entity + events: list[Event] + # Status of the entity + status: list[str] | None = None + # WHOIS server for the entity + port43: str + # Type of the object (always "entity" for Entity) + objectClassName: str + # Additional remarks about the entity + remarks: list[Remark] | None = None + # Nested entities (e.g., contacts within an organization) + entities: list['Entity'] | None = None + + +class IPNetwork(BaseModel): + """Represents an IP network in the RDAP response.""" + # list of conformance levels + rdapConformance: list[str] = Field(default_factory=list) + # Notices related to the IP network + notices: list[Notice] = Field(default_factory=list) + # A unique identifier for the IP network + handle: str + # The first IP address in the network range + startAddress: str + # The last IP address in the network range + endAddress: str + # IP version (v4 or v6) + ipVersion: str + # Name of the network + name: str + # Type of the network allocation + type: str + # Handle of the parent network + parentHandle: str + # Additional remarks about the network + remarks: list[Remark] | None = None + # Events associated with the network + events: list[Event] + # Links related to the network + links: list[Link] + # Entities associated with the network + entities: list[Entity] + # WHOIS server for the network + port43: str + # Status of the network + status: list[str] + # Type of the object (always "ip network" for IPNetwork) + objectClassName: str + # CIDR notation for the network + cidr0_cidrs: list[dict] + # Origin AS numbers for the network + arin_originas0_originautnums: list + +class Domain(BaseModel): + """Represents a domain name in the RDAP response.""" + # list of conformance levels + rdapConformance: list[str] = Field(default_factory=list) + # Notices related to the domain + notices: list[Notice] = Field(default_factory=list) + # A unique identifier for the domain + handle: str + # The domain name in LDH (Letter Digit Hyphen) format + ldhName: str + # Events associated with the domain + events: list[Event] + # Links related to the domain + links: list[Link] + # Entities associated with the domain + entities: list[Entity] + # WHOIS server for the domain + port43: str + # Network information for the domain + network: IPNetwork + # Type of the object (always "domain" for Domain) + objectClassName: str + +class AutNum(BaseModel): + """Represents an Autonomous System Number in the RDAP response.""" + # list of conformance levels + rdapConformance: list[str] = Field(default_factory=list) + # Notices related to the AS number + notices: list[Notice] = Field(default_factory=list) + # A unique identifier for the AS number + handle: str + # The starting AS number in the range + startAutnum: int + # The ending AS number in the range (same as startAutnum for single AS) + endAutnum: int + # Name of the AS + name: str + # WHOIS server for the AS number + port43: str + + # Type of the object (always "autnum" for AutNum) + objectClassName: str + + # Events associated with the AS number + events: list[Event] = Field(default_factory=list) + # Links related to the AS number + links: list[Link] = Field(default_factory=list) + # Entities associated with the AS number + entities: list[Entity] = Field(default_factory=list) + # Status of the AS number + status: list[str] = Field(default_factory=list) + +Entity.update_forward_refs() \ No newline at end of file diff --git a/rdap/schema/source.py b/rdap/schema/source.py index e69de29..63cb762 100644 --- a/rdap/schema/source.py +++ b/rdap/schema/source.py @@ -0,0 +1,25 @@ +import rdap.schema.arin as arin + +__all__ = [ + "SCHEMAS_BY_RIR", + "ip_network_model", + "domain_model", + "autnum_model", + "entity_model", +] + +SCHEMAS_BY_RIR = { + "arin": arin, +} + +def ip_network_model(rir:str): + return SCHEMAS_BY_RIR[rir].IPNetwork + +def domain_model(rir:str): + return SCHEMAS_BY_RIR[rir].Domain + +def autnum_model(rir:str): + return SCHEMAS_BY_RIR[rir].AutNum + +def entity_model(rir:str): + return SCHEMAS_BY_RIR[rir].Entity From 8b6ad349962522c4352712b65c2d92d4911ceb59 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Wed, 10 Jul 2024 13:57:56 +0000 Subject: [PATCH 09/28] start framework for normalization --- rdap/cli.py | 5 ++++ rdap/normalize/__init__.py | 34 +++++++++++++++++++++++++ rdap/normalize/arin.py | 51 ++++++++++++++++++++++++++++++++++++++ rdap/objects.py | 7 +++--- rdap/schema/arin.py | 31 +++-------------------- rdap/schema/normalized.py | 4 +-- 6 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 rdap/normalize/__init__.py create mode 100644 rdap/normalize/arin.py diff --git a/rdap/cli.py b/rdap/cli.py index 8911086..a91c356 100644 --- a/rdap/cli.py +++ b/rdap/cli.py @@ -49,6 +49,9 @@ def main(argv=None): parser.add_argument( "--parse", action="store_true", help="parse data into object before display" ) + parser.add_argument( + "--normalize", action="store_true", help="normalize data before display" + ) parser.add_argument("--rir", action="store_true", help="display rir", default=False) parser.add_argument( "--write-bootstrap-data", @@ -80,6 +83,8 @@ def main(argv=None): print(f"rir: {obj.get_rir()}") if argd.get("parse", False): print(codec.dumps(obj.parsed())) + elif argd.get("normalize", False): + obj.normalized else: print(codec.dumps(obj.data)) diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py new file mode 100644 index 0000000..5c7beba --- /dev/null +++ b/rdap/normalize/__init__.py @@ -0,0 +1,34 @@ +""" +Contains a set of functions to parse various rdap data properties +""" + +import rdap.normalize.arin as arin + +import rdap.schema.normalized as schema +from rdap.schema.source import autnum_model + +__all__ = [ + "normalize", +] + +HANDLERS = { + "arin": arin +} + +def normalize(data: dict, rir: str, typ: str) -> dict: + """ + Normalize data based on RIR + + Will return a normalized dict based on the RIR + """ + handler = HANDLERS.get(rir) + + if not handler: + raise ValueError(f"RIR {rir} not supported") + + if typ == "autnum": + model = autnum_model(rir)(**data) + org_name = handler.org_name(model) + print("org_name: ", org_name) + + return data \ No newline at end of file diff --git a/rdap/normalize/arin.py b/rdap/normalize/arin.py new file mode 100644 index 0000000..cd0a840 --- /dev/null +++ b/rdap/normalize/arin.py @@ -0,0 +1,51 @@ +""" +Rdap data parsers for ARIN data +""" + +import rdap.schema.arin as schema + +__all__ = [ + "org_name_from_entity", + "org_name" +] + +def org_name_from_entity(entity: schema.Entity) -> str | None: + """ + Will parse an org name from an entity + + Will return the org name if it can be found, otherwise None + """ + # org name will be through entities + # looking for vcardArray with ["kind", {}, "text", org] to + # establish the org entry + # + # in the same vcardArray, looking for ["fn", {}, "text", name] + + kind_is_org = False + org_name = None + + for vcard in entity.vcardArray: + if vcard[0] == "kind" and vcard[3] == "org": + kind_is_org = True + if vcard[0] == "fn": + org_name = vcard[3] + + if kind_is_org and org_name: + return org_name + + return None + +def org_name(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an org name from an object + + Will return the org name if it can be found, if no org name + can be found, will return entity name or None + """ + + for _entity in entity.entities: + name = org_name_from_entity(_entity) + if name: + return name + + return entity.name or None \ No newline at end of file diff --git a/rdap/objects.py b/rdap/objects.py index 2e30e43..5f9cad2 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -1,6 +1,6 @@ from rdap.exceptions import RdapHTTPError, RdapNotFoundError -from rdap.schema.source import autnum_model +from rdap.normalize import normalize def rir_from_domain(domain): """Gets the RIR from a URL or domain, if possible""" @@ -206,10 +206,9 @@ def __init__(self, data, rdapc=None): super().__init__(data, rdapc) - @property - def model(self): - return autnum_model(self.get_rir())(**self._data) + def normalized(self): + normalize(self._data, self.get_rir(), "autnum") class RdapNetwork(RdapObject): def __init__(self, data, rdapc=None): diff --git a/rdap/schema/arin.py b/rdap/schema/arin.py index 2211b03..2c1b784 100644 --- a/rdap/schema/arin.py +++ b/rdap/schema/arin.py @@ -34,32 +34,6 @@ class VCardValue(BaseModel): # Types associated with the vCard value (e.g., "work", "voice" for telephone) type: list[str] | None = None -class VCardEntry(BaseModel): - """Represents a single entry in a vCard.""" - # The name of the vCard property (e.g., "version", "fn", "adr") - name: str - # Metadata associated with the entry - meta: dict = Field(default_factory=dict) - # The value of the vCard property - value: str | list[str] | VCardValue - -class VCard(BaseModel): - """Represents a vCard (contact information) in the RDAP response.""" - version: VCardEntry - # Formatted Name - fn: VCardEntry - # Address - adr: VCardEntry | None = None - # Kind of object (e.g., individual, org, group) - kind: VCardEntry | None = None - email: VCardEntry | None = None - # Telephone - tel: VCardEntry | None = None - # Name components - n: VCardEntry | None = None - # Organization - org: VCardEntry | None = None - class Remark(BaseModel): """Represents a remark or comment in the RDAP response.""" # The title of the remark @@ -72,7 +46,7 @@ class Entity(BaseModel): # A unique identifier for the entity handle: str # Contact information in vCard format - vcardArray: list[str | list[VCardEntry] | list[Any]] = Field(default_factory=list) + vcardArray: list[str | list[list[str | dict | list]]] = Field(default_factory=list) # Roles of the entity (e.g., registrant, technical, administrative) roles: list[str] # Links related to the entity @@ -182,4 +156,5 @@ class AutNum(BaseModel): # Status of the AS number status: list[str] = Field(default_factory=list) -Entity.update_forward_refs() \ No newline at end of file + +Entity.model_rebuild() \ No newline at end of file diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index bd51499..9a56b46 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -2,7 +2,7 @@ Pydantic schemas for normalized RDAP data """ -import datetime +from datetime import datetime import pydantic import enum import ipaddress @@ -118,7 +118,7 @@ class IPNetwork(pydantic.BaseModel): version: IP_VERSION name: str type: str - STATUS: STATUS + status: STATUS parent: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) From e42f69830511679dd62ebf9525a89b81b9f68c9c Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 11 Jul 2024 15:15:52 +0000 Subject: [PATCH 10/28] arin network normalization with geo data --- poetry.lock | 16 +++++- pyproject.toml | 1 + rdap/cli.py | 20 ++++---- rdap/client.py | 26 +++++----- rdap/context.py | 43 +++++++++++++++++ rdap/normalize/__init__.py | 52 ++++++++++++++++++-- rdap/normalize/arin.py | 99 ++++++++++++++++++++++++++++++++++++++ rdap/normalize/geo.py | 99 ++++++++++++++++++++++++++++++++++++++ rdap/schema/normalized.py | 36 ++++++++++++-- 9 files changed, 364 insertions(+), 28 deletions(-) create mode 100644 rdap/context.py create mode 100644 rdap/normalize/geo.py diff --git a/poetry.lock b/poetry.lock index e7e6734..f4c7316 100644 --- a/poetry.lock +++ b/poetry.lock @@ -396,6 +396,20 @@ python-dateutil = ">=2.8.1" [package.extras] dev = ["flake8", "markdown", "twine", "wheel"] +[[package]] +name = "googlemaps" +version = "4.10.0" +description = "Python client library for Google Maps Platform" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "googlemaps-4.10.0.tar.gz", hash = "sha256:3055fcbb1aa262a9159b589b5e6af762b10e80634ae11c59495bd44867e47d88"}, +] + +[package.dependencies] +requests = ">=2.20.0,<3.0" + [[package]] name = "identify" version = "2.6.0" @@ -1427,4 +1441,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "107cf60642ce375bceda9d23dff9f7b5957b30698122196d0f32a5f336e29f2d" +content-hash = "b732318e919c5196170885f6cac2a604d2d536436caa9b558ad70aad94533491" diff --git a/pyproject.toml b/pyproject.toml index 75cd167..5d96e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ python = "^3.8" requests = ">=2.25.1" munge = {version = ">=1.3", extras = ["yaml"]} pydantic = ">=2.8.2" +googlemaps = ">=4.10" [tool.poetry.dev-dependencies] # testing diff --git a/rdap/cli.py b/rdap/cli.py index a91c356..173f28f 100644 --- a/rdap/cli.py +++ b/rdap/cli.py @@ -6,6 +6,7 @@ import rdap from rdap.config import Config +from rdap.context import RdapRequestContext def add_options(parser, options): @@ -78,15 +79,16 @@ def main(argv=None): codec = munge.get_codec(output_format)() for each in argd["query"]: - obj = client.get(each) - if argd.get("rir", False): - print(f"rir: {obj.get_rir()}") - if argd.get("parse", False): - print(codec.dumps(obj.parsed())) - elif argd.get("normalize", False): - obj.normalized - else: - print(codec.dumps(obj.data)) + with RdapRequestContext(): + obj = client.get(each) + if argd.get("rir", False): + print(f"rir: {obj.get_rir()}") + if argd.get("parse", False): + print(codec.dumps(obj.parsed())) + elif argd.get("normalize", False): + obj.normalized + else: + print(codec.dumps(obj.data)) if argd.get("show_requests", False): print("# Requests") diff --git a/rdap/client.py b/rdap/client.py index a074637..e5d6e63 100644 --- a/rdap/client.py +++ b/rdap/client.py @@ -10,6 +10,7 @@ import rdap import rdap.bootstrap +from rdap.context import RdapRequestContext from rdap.config import Config from rdap.exceptions import RdapHTTPError, RdapNotFoundError from rdap.objects import RdapAsn, RdapDomain, RdapEntity, RdapNetwork @@ -129,20 +130,21 @@ def __init__(self, config=None, config_dir=None): ) def _get(self, url): - res = self.http.get(url, timeout=self.timeout) - for redir in res.history: - self._history.append((strip_auth(redir.url), redir.status_code)) - self._history.append((strip_auth(res.url), res.status_code)) + with RdapRequestContext(url): + res = self.http.get(url, timeout=self.timeout) + for redir in res.history: + self._history.append((strip_auth(redir.url), redir.status_code)) + self._history.append((strip_auth(res.url), res.status_code)) - if res.status_code == 200: - return res + if res.status_code == 200: + return res - msg = "RDAP lookup to {} returned {}".format( - strip_auth(res.url), res.status_code - ) - if res.status_code == 404: - raise RdapNotFoundError(msg) - raise RdapHTTPError(msg) + msg = "RDAP lookup to {} returned {}".format( + strip_auth(res.url), res.status_code + ) + if res.status_code == 404: + raise RdapNotFoundError(msg) + raise RdapHTTPError(msg) def asn_url(self, asn): """Gets the correct url for specified ASN.""" diff --git a/rdap/context.py b/rdap/context.py new file mode 100644 index 0000000..f0fc606 --- /dev/null +++ b/rdap/context.py @@ -0,0 +1,43 @@ +from contextvars import ContextVar +import pydantic +from datetime import datetime + +__all__ = [ + "rdap_request", + "RdapRequestContext", + "RdapRequestState", +] + +class RdapRequestState(pydantic.BaseModel): + urls: list[str] = pydantic.Field(default_factory=list) + +# context that holds the currently requested rdap url + +rdap_request = ContextVar("rdap_request", default=RdapRequestState()) + +# context manager to set the rdap url +# can be nested + +class RdapRequestContext: + + def __init__(self, url:str = None): + self.url = url + self.token = None + + def __enter__(self): + + # get existing state + + state = rdap_request.get() + + if state and self.url: + state.urls.append(self.url) + else: + state = RdapRequestState(urls=[self.url] if self.url else []) + self.token = rdap_request.set(state) + + + + def __exit__(self, *exc): + if self.token: + rdap_request.reset(self.token) \ No newline at end of file diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py index 5c7beba..9845704 100644 --- a/rdap/normalize/__init__.py +++ b/rdap/normalize/__init__.py @@ -3,9 +3,12 @@ """ import rdap.normalize.arin as arin +import rdap.normalize.geo as geo import rdap.schema.normalized as schema from rdap.schema.source import autnum_model +from rdap.context import rdap_request, RdapRequestState + __all__ = [ "normalize", @@ -15,6 +18,26 @@ "arin": arin } +def get_sources( + state: RdapRequestState, + handle: str, + entity: schema.Network | schema.IPNetwork | schema.Domain | schema.Entity +) -> list[schema.Source]: + + sources = [] + + print(state.model_dump()) + + sources.append(schema.Source( + created = entity.created, + updated = entity.updated, + handle = handle, + urls = state.urls + )) + + return sources + + def normalize(data: dict, rir: str, typ: str) -> dict: """ Normalize data based on RIR @@ -26,9 +49,32 @@ def normalize(data: dict, rir: str, typ: str) -> dict: if not handler: raise ValueError(f"RIR {rir} not supported") + current_rdap_request = rdap_request.get() + if typ == "autnum": - model = autnum_model(rir)(**data) - org_name = handler.org_name(model) - print("org_name: ", org_name) + rdap_autnum = autnum_model(rir)(**data) + org_name = handler.org_name(rdap_autnum) + org = schema.Organization(name=org_name) + address = handler.address(rdap_autnum) + + + net = schema.Network( + created = rdap_autnum.events[-1].eventDate, + updated = rdap_autnum.events[0].eventDate, + name = rdap_autnum.name, + organization = org, + asn = rdap_autnum.startAutnum, + contacts = handler.contacts(rdap_autnum), + ) + + location = geo.normalize(address, net.updated) + if location: + net.location = location + + if current_rdap_request: + net.sources = get_sources(current_rdap_request, rdap_autnum.handle, net) + + print(net.model_dump_json(indent=2)) + return data \ No newline at end of file diff --git a/rdap/normalize/arin.py b/rdap/normalize/arin.py index cd0a840..f3a42df 100644 --- a/rdap/normalize/arin.py +++ b/rdap/normalize/arin.py @@ -2,6 +2,10 @@ Rdap data parsers for ARIN data """ +from rdap.schema.normalized import ( + Contact, +) + import rdap.schema.arin as schema __all__ = [ @@ -9,6 +13,101 @@ "org_name" ] +def address_from_entity(entity: schema.Entity) -> str | None: + """ + Will parse an address from an entity + + Will return the address if it can be found, otherwise None + """ + # address will be through entities + # looking for vcardArray with ["adr", {"label": address}, "text"] to + # establish the address entry + + for vcard in entity.vcardArray[1:]: + for vcard_entry in vcard: + if vcard_entry[0] == "adr": + return vcard_entry[1].get("label") + + return None + +def address(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an address from an object + + Will return the address if it can be found, if no address + can be found, will return None + """ + + for _entity in entity.entities: + address = address_from_entity(_entity) + if address: + return address + + return None + +def contacts_from_entity(entity: schema.Entity, deep:bool = True) -> list[Contact]: + + """ + Will parse contacts from an entity + + Will return a list of contacts if it can be found, otherwise an empty list + + vcard entriews that are considered: + + ["email", {}, "text", email] + ["tel", {}, "text", phone] + ["fn", {}, "text", name] + + A contact is considered if `fn` and either of `email` or `tel` is present + + If deep is set to True, will also check nessted entities. + """ + + contacts = [] + + for vcard in entity.vcardArray[1:]: + + contact = Contact( + name = "", + roles = getattr(entity, "roles", []) + ) + for vcard_entry in vcard: + if vcard_entry[0] == "fn": + contact.name = vcard_entry[3] + if vcard_entry[0] == "email": + contact.email = vcard_entry[3] + if vcard_entry[0] == "tel": + contact.phone = vcard_entry[3] + + if contact.name and (contact.email or contact.phone): + contacts.append(contact) + + if deep and getattr(entity, "entities", None): + for _entity in entity.entities: + contacts.extend(contacts_from_entity(_entity, deep=True)) + + # remove dupes + contacts = list(set(contacts)) + + return contacts + +def contacts(entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: + + """ + Will parse contacts from an object + + Will return a list of contacts if it can be found, otherwise an empty list + + If deep is set to True, will also check nessted entities. + """ + + contacts = [] + + for _entity in entity.entities: + contacts.extend(contacts_from_entity(_entity, deep=deep)) + + return contacts + def org_name_from_entity(entity: schema.Entity) -> str | None: """ Will parse an org name from an entity diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py new file mode 100644 index 0000000..f26cc28 --- /dev/null +++ b/rdap/normalize/geo.py @@ -0,0 +1,99 @@ +""" +Uses googlemaps to resolve address to geolocation +and into fields +""" + +import os +import googlemaps + +from datetime import datetime +from rdap.schema.normalized import Location, GeoLocation + +GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY") + +class RequestError(Exception): + pass + +class Timeout(Exception): + pass + +class NotFound(KeyError): + pass + +def get_client(key:str = GOOGLE_MAPS_API_KEY): + return googlemaps.Client(key) + + +def lookup(formatted_address:str, client = None) -> dict: + """ + Return the latitude, longitude field values of the specified + location. + """ + + # TODO: cache results + + if not client: + client = get_client() + + try: + result = client.geocode( + formatted_address, + ) + except ( + googlemaps.exceptions.HTTPError, + googlemaps.exceptions.ApiError, + googlemaps.exceptions.TransportError, + ) as exc: + raise RequestError(exc) + except googlemaps.exceptions.Timeout: + raise Timeout() + + if not result: + raise NotFound() + + return result[0] + +def normalize(formatted_address:str, date:datetime = None, client=None) -> Location: + + """ + Takes a formatted address and returns a normalized location object + """ + + result = lookup(formatted_address, client) + + city = None + postal_code = None + country = None + floor = None + suite = None + + for component in result.get("address_components", []): + if "country" in component.get("types", []): + country = component.get("short_name") + + if "postal_code" in component.get("types", []): + postal_code = component.get("long_name") + + if "locality" in component.get("types", []): + city = component.get("long_name") + + if "floor" in component.get("types", []): + floor = component.get("long_name") + + if "subpremise" in component.get("types", []): + suite = component.get("long_name") + + + return Location( + updated = date or datetime.now(), + country = country, + city = city, + postal_code = postal_code, + floor = floor, + suite = suite, + address = result.get("formatted_address"), + geo = GeoLocation( + latitude = result.get("geometry").get("location").get("lat"), + longitude = result.get("geometry").get("location").get("lng") + ), + ) \ No newline at end of file diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 9a56b46..4f91b90 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -3,6 +3,7 @@ """ from datetime import datetime +from typing import Any import pydantic import enum import ipaddress @@ -40,6 +41,11 @@ class ROLE(str, enum.Enum): admin = "admin" policy = "policy" technical = "technical" + registrant = "registrant" + +NORMALIZED_ROLES = { + "administrative": "admin" +} class DNSSEC(str, enum.Enum): secure = "secure" @@ -70,13 +76,31 @@ class Contact(pydantic.BaseModel): """ Describes a point of contact """ - created: datetime - updated: datetime + #created: datetime + #updated: datetime name: str roles: list[ROLE] = pydantic.Field(default_factory=list) phone: str | None = None email: str | None = None + @pydantic.model_validator(mode="before") + @classmethod + def normalize_roles(cls, data: Any) -> Any: + + roles = [] + + for role in data.get("roles", []): + role = NORMALIZED_ROLES.get(role, role) + roles.append(role) + + data["roles"] = roles + + return data + + + def __hash__(self): + return f"{self.name}-{self.email}-{self.phone}: {self.roles}".__hash__() + class Source(pydantic.BaseModel): """ Describes a source of rdap data @@ -105,7 +129,7 @@ class Network(pydantic.BaseModel): asn: int name: str organization: Organization - location: Location + location: Location | None = None contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) @@ -114,6 +138,8 @@ class IPNetwork(pydantic.BaseModel): """ Describes an IP network """ + created: datetime + updated: datetime prefix: ipaddress.IPv4Network | ipaddress.IPv6Network version: IP_VERSION name: str @@ -127,6 +153,8 @@ class Entity(pydantic.BaseModel): """ Describes an entity """ + created: datetime + updated: datetime name: str organization: Organization location: Location @@ -143,6 +171,8 @@ class Domain(pydantic.BaseModel): """ Describes a domain """ + created: datetime + updated: datetime name: str handle: str dns_sec: DNSSEC From 2025cbdd3c4377a79a5fda6e5fe72c381db24cfc Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Tue, 16 Jul 2024 17:23:49 +0000 Subject: [PATCH 11/28] finalize normalization base --- rdap/cli.py | 4 +- rdap/client.py | 8 +- rdap/context.py | 32 +++- rdap/normalize/__init__.py | 207 ++++++++++++++++++---- rdap/normalize/arin.py | 150 ---------------- rdap/normalize/base.py | 284 +++++++++++++++++++++++++++++++ rdap/normalize/geo.py | 19 ++- rdap/objects.py | 17 +- rdap/schema/normalized.py | 20 ++- rdap/schema/{arin.py => rdap.py} | 87 +++++++--- rdap/schema/source.py | 9 +- 11 files changed, 610 insertions(+), 227 deletions(-) delete mode 100644 rdap/normalize/arin.py create mode 100644 rdap/normalize/base.py rename rdap/schema/{arin.py => rdap.py} (66%) diff --git a/rdap/cli.py b/rdap/cli.py index 173f28f..08eaac8 100644 --- a/rdap/cli.py +++ b/rdap/cli.py @@ -79,14 +79,14 @@ def main(argv=None): codec = munge.get_codec(output_format)() for each in argd["query"]: - with RdapRequestContext(): + with RdapRequestContext(client=client): obj = client.get(each) if argd.get("rir", False): print(f"rir: {obj.get_rir()}") if argd.get("parse", False): print(codec.dumps(obj.parsed())) elif argd.get("normalize", False): - obj.normalized + print(codec.dumps(obj.normalized)) else: print(codec.dumps(obj.data)) diff --git a/rdap/client.py b/rdap/client.py index e5d6e63..cc6fb55 100644 --- a/rdap/client.py +++ b/rdap/client.py @@ -130,12 +130,14 @@ def __init__(self, config=None, config_dir=None): ) def _get(self, url): - with RdapRequestContext(url): + with RdapRequestContext(url) as ctx: res = self.http.get(url, timeout=self.timeout) for redir in res.history: self._history.append((strip_auth(redir.url), redir.status_code)) self._history.append((strip_auth(res.url), res.status_code)) + ctx.push_url(strip_auth(res.url)) + if res.status_code == 200: return res @@ -245,6 +247,8 @@ def get(self, query): if qstr.startswith("as"): return self.get_asn(qstr[2:]) + return self.get_entity(qstr, self.url) + raise NotImplementedError(f"unknown query {query}") @lru_cache(maxsize=1024) @@ -317,7 +321,7 @@ def get_ip(self, address): query = f"/ip/{address}" return RdapNetwork(self._rdap_get(query).json(), self) - def get_entity(self, handle, base_url): + def get_entity(self, handle, base_url = None): """ get entity information in object form """ diff --git a/rdap/context.py b/rdap/context.py index f0fc606..2a027c9 100644 --- a/rdap/context.py +++ b/rdap/context.py @@ -2,14 +2,28 @@ import pydantic from datetime import datetime +from rdap.schema.rdap import Entity, AutNum, IPNetwork, Domain + __all__ = [ "rdap_request", "RdapRequestContext", "RdapRequestState", ] -class RdapRequestState(pydantic.BaseModel): +class RdapSource(pydantic.BaseModel): urls: list[str] = pydantic.Field(default_factory=list) + handle: str | None = None + created: datetime | None = None + updated: datetime | None = None + +class RdapRequestState(pydantic.BaseModel): + sources: list[RdapSource] = pydantic.Field(default_factory=list) + client: object | None = None #RdapClient + + def update_source(self, entity:Entity | AutNum | IPNetwork | Domain): + self.sources[-1].handle = entity.handle + self.sources[-1].created = entity.events[-1].eventDate + self.sources[-1].updated = entity.events[0].eventDate # context that holds the currently requested rdap url @@ -20,9 +34,10 @@ class RdapRequestState(pydantic.BaseModel): class RdapRequestContext: - def __init__(self, url:str = None): + def __init__(self, url:str = None, client:object = None): self.url = url self.token = None + self.client = client def __enter__(self): @@ -31,13 +46,20 @@ def __enter__(self): state = rdap_request.get() if state and self.url: - state.urls.append(self.url) + state.sources.append(RdapSource(urls=[self.url])) else: - state = RdapRequestState(urls=[self.url] if self.url else []) + state = RdapRequestState(sources=[RdapSource(urls=[self.url] if self.url else [])]) self.token = rdap_request.set(state) + if self.client: + state.client = self.client + return self def __exit__(self, *exc): if self.token: - rdap_request.reset(self.token) \ No newline at end of file + rdap_request.reset(self.token) + + def push_url(self, url:str): + state = rdap_request.get() + state.sources[-1].urls.append(url) \ No newline at end of file diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py index 9845704..e0793c6 100644 --- a/rdap/normalize/__init__.py +++ b/rdap/normalize/__init__.py @@ -2,11 +2,19 @@ Contains a set of functions to parse various rdap data properties """ -import rdap.normalize.arin as arin +import json + +import rdap.normalize.base as base import rdap.normalize.geo as geo import rdap.schema.normalized as schema -from rdap.schema.source import autnum_model +from rdap.schema.source import ( + autnum_model, + entity_model, + ip_network_model, + domain_model, +) + from rdap.context import rdap_request, RdapRequestState @@ -15,7 +23,13 @@ ] HANDLERS = { - "arin": arin + "arin": base, + "ripe": base, + "apnic": base, + "afrinic": base, + "lacnic": base, + # domains are weird. + None: base } def get_sources( @@ -25,15 +39,21 @@ def get_sources( ) -> list[schema.Source]: sources = [] + + for source in state.sources: - print(state.model_dump()) + if not source.urls or not source.handle: + continue + + source = schema.Source( + handle = source.handle, + created = source.created, + updated = source.updated, + urls = source.urls, + ) + + sources.append(source) - sources.append(schema.Source( - created = entity.created, - updated = entity.updated, - handle = handle, - urls = state.urls - )) return sources @@ -44,37 +64,162 @@ def normalize(data: dict, rir: str, typ: str) -> dict: Will return a normalized dict based on the RIR """ - handler = HANDLERS.get(rir) + if typ == "autnum": + return normalize_autnum(data, rir) + elif typ == "entity": + return normalize_entity(data, rir) + elif typ == "ip": + return normalize_ip(data, rir) + elif typ == "domain": + return normalize_domain(data, rir) + else: + raise ValueError(f"Type {typ} not supported") + +def normalize_autnum(data: dict, rir: str) -> dict: + """ + Normalize data based on RIR: Autnum + + Will return a normalized dict based on the RIR + """ + handler = HANDLERS.get(rir) if not handler: raise ValueError(f"RIR {rir} not supported") current_rdap_request = rdap_request.get() - if typ == "autnum": - rdap_autnum = autnum_model(rir)(**data) - org_name = handler.org_name(rdap_autnum) - org = schema.Organization(name=org_name) - address = handler.address(rdap_autnum) + rdap_autnum = autnum_model(rir)(**data) + current_rdap_request.update_source(rdap_autnum) - net = schema.Network( - created = rdap_autnum.events[-1].eventDate, - updated = rdap_autnum.events[0].eventDate, - name = rdap_autnum.name, - organization = org, - asn = rdap_autnum.startAutnum, - contacts = handler.contacts(rdap_autnum), - ) + org_name = handler.org_name(rdap_autnum) + org = schema.Organization(name=org_name) + address = handler.address(rdap_autnum) + + net = schema.Network( + created = rdap_autnum.events[-1].eventDate, + updated = rdap_autnum.events[0].eventDate, + name = rdap_autnum.name, + organization = org, + asn = rdap_autnum.startAutnum, + contacts = handler.contacts(rdap_autnum), + ) + + location = geo.normalize(address, net.updated) + if location: + net.location = location + + if current_rdap_request: + net.sources = get_sources(current_rdap_request, rdap_autnum.handle, net) + + return json.loads(net.model_dump_json()) + +def normalize_ip(data: dict, rir: str) -> dict: + """ + Normalize data based on RIR: IPNetwork + + Will return a normalized dict based on the RIR + """ + handler = HANDLERS.get(rir) + if not handler: + raise ValueError(f"RIR {rir} not supported") + + current_rdap_request = rdap_request.get() + + rdap_ip_network = ip_network_model(rir)(**data) + + current_rdap_request.update_source(rdap_ip_network) + + prefix = handler.prefix(rdap_ip_network) + + net = schema.IPNetwork( + created = rdap_ip_network.events[-1].eventDate, + updated = rdap_ip_network.events[0].eventDate, + name = rdap_ip_network.name, + prefix = prefix, + parent= handler.parent_prefix(rdap_ip_network), + version = prefix.version, + type = rdap_ip_network.type, + # TODO: What happens if more than one status is in there? + status = rdap_ip_network.status[0] if rdap_ip_network.status else None, + contacts = handler.contacts(rdap_ip_network), + ) + + if current_rdap_request: + net.sources = get_sources(current_rdap_request, rdap_ip_network.handle, net) + + return json.loads(net.model_dump_json()) + +def normalize_domain(data: dict, rir: str) -> dict: + """ + Normalize data based on RIR: Domain + + Will return a normalized dict based on the RIR + """ + handler = HANDLERS.get(rir) + if not handler: + raise ValueError(f"RIR {rir} not supported") + + current_rdap_request = rdap_request.get() + + rdap_domain = domain_model(rir)(**data) + + current_rdap_request.update_source(rdap_domain) + + net = schema.Domain( + created = rdap_domain.events[-1].eventDate, + updated = rdap_domain.events[0].eventDate, + name = rdap_domain.ldhName, + handle = rdap_domain.handle, + dns_sec = handler.secure_dns(rdap_domain), + contacts = handler.contacts(rdap_domain), + nameservers = handler.nameservers(rdap_domain), + ) + + if current_rdap_request: + net.sources = get_sources(current_rdap_request, rdap_domain.handle, net) + + return json.loads(net.model_dump_json()) + + +def normalize_entity(data: dict, rir: str) -> dict: + """ + Normalize data based on RIR: Entity + + Will return a normalized dict based on the RIR + """ + handler = HANDLERS.get(rir) + if not handler: + raise ValueError(f"RIR {rir} not supported") + + current_rdap_request = rdap_request.get() + + rdap_entity = entity_model(rir)(**data) + + current_rdap_request.update_source(rdap_entity) + + org_name = handler.org_name_from_entity(rdap_entity) + + if org_name: + org = schema.Organization(name=org_name) + else: + org = None - location = geo.normalize(address, net.updated) - if location: - net.location = location + address = handler.address_from_entity(rdap_entity) - if current_rdap_request: - net.sources = get_sources(current_rdap_request, rdap_autnum.handle, net) + entity = schema.Entity( + created = rdap_entity.events[-1].eventDate, + updated = rdap_entity.events[0].eventDate, + name = rdap_entity.handle, + organization = org, + contacts = handler.contacts_from_entity(rdap_entity), + ) - print(net.model_dump_json(indent=2)) + location = geo.normalize(address, entity.updated) + if location: + entity.location = location + if current_rdap_request: + entity.sources = get_sources(current_rdap_request, rdap_entity.handle, entity) - return data \ No newline at end of file + return json.loads(entity.model_dump_json()) \ No newline at end of file diff --git a/rdap/normalize/arin.py b/rdap/normalize/arin.py deleted file mode 100644 index f3a42df..0000000 --- a/rdap/normalize/arin.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Rdap data parsers for ARIN data -""" - -from rdap.schema.normalized import ( - Contact, -) - -import rdap.schema.arin as schema - -__all__ = [ - "org_name_from_entity", - "org_name" -] - -def address_from_entity(entity: schema.Entity) -> str | None: - """ - Will parse an address from an entity - - Will return the address if it can be found, otherwise None - """ - # address will be through entities - # looking for vcardArray with ["adr", {"label": address}, "text"] to - # establish the address entry - - for vcard in entity.vcardArray[1:]: - for vcard_entry in vcard: - if vcard_entry[0] == "adr": - return vcard_entry[1].get("label") - - return None - -def address(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: - """ - Will parse an address from an object - - Will return the address if it can be found, if no address - can be found, will return None - """ - - for _entity in entity.entities: - address = address_from_entity(_entity) - if address: - return address - - return None - -def contacts_from_entity(entity: schema.Entity, deep:bool = True) -> list[Contact]: - - """ - Will parse contacts from an entity - - Will return a list of contacts if it can be found, otherwise an empty list - - vcard entriews that are considered: - - ["email", {}, "text", email] - ["tel", {}, "text", phone] - ["fn", {}, "text", name] - - A contact is considered if `fn` and either of `email` or `tel` is present - - If deep is set to True, will also check nessted entities. - """ - - contacts = [] - - for vcard in entity.vcardArray[1:]: - - contact = Contact( - name = "", - roles = getattr(entity, "roles", []) - ) - for vcard_entry in vcard: - if vcard_entry[0] == "fn": - contact.name = vcard_entry[3] - if vcard_entry[0] == "email": - contact.email = vcard_entry[3] - if vcard_entry[0] == "tel": - contact.phone = vcard_entry[3] - - if contact.name and (contact.email or contact.phone): - contacts.append(contact) - - if deep and getattr(entity, "entities", None): - for _entity in entity.entities: - contacts.extend(contacts_from_entity(_entity, deep=True)) - - # remove dupes - contacts = list(set(contacts)) - - return contacts - -def contacts(entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: - - """ - Will parse contacts from an object - - Will return a list of contacts if it can be found, otherwise an empty list - - If deep is set to True, will also check nessted entities. - """ - - contacts = [] - - for _entity in entity.entities: - contacts.extend(contacts_from_entity(_entity, deep=deep)) - - return contacts - -def org_name_from_entity(entity: schema.Entity) -> str | None: - """ - Will parse an org name from an entity - - Will return the org name if it can be found, otherwise None - """ - # org name will be through entities - # looking for vcardArray with ["kind", {}, "text", org] to - # establish the org entry - # - # in the same vcardArray, looking for ["fn", {}, "text", name] - - kind_is_org = False - org_name = None - - for vcard in entity.vcardArray: - if vcard[0] == "kind" and vcard[3] == "org": - kind_is_org = True - if vcard[0] == "fn": - org_name = vcard[3] - - if kind_is_org and org_name: - return org_name - - return None - -def org_name(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: - """ - Will parse an org name from an object - - Will return the org name if it can be found, if no org name - can be found, will return entity name or None - """ - - for _entity in entity.entities: - name = org_name_from_entity(_entity) - if name: - return name - - return entity.name or None \ No newline at end of file diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py new file mode 100644 index 0000000..4529c2f --- /dev/null +++ b/rdap/normalize/base.py @@ -0,0 +1,284 @@ +""" +Rdap data parsers for ARIN data +""" + +import ipaddress +from rdap.exceptions import RdapHTTPError + +from rdap.schema.normalized import ( + Contact, + Nameserver, + DNSSEC +) + +from rdap.context import rdap_request, RdapRequestState, RdapRequestContext + +import rdap.schema.rdap as schema + +__all__ = [ + "org_name_from_entity", + "org_name", + "contacts_from_entity", + "contacts", + "address_from_entity", + "address", + "prefix", + "parent_prefix", + "secure_dns", + "nameservers", +] + +def address_from_entity(entity: schema.Entity) -> str | None: + """ + Will parse an address from an entity + + Will return the address if it can be found, otherwise None + """ + # address will be through entities + # looking for vcardArray with ["adr", {"label": address}, "text"] to + # establish the address entry + + for vcard in entity.vcardArray[1:]: + for vcard_entry in vcard: + if vcard_entry[0] == "adr": + return vcard_entry[1].get("label") + + return None + +def address(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an address from an object + + Will return the address if it can be found, if no address + can be found, will return None + """ + + for _entity in entity.entities: + address = address_from_entity(_entity) + if address: + return address + + return None + +def contacts_from_entity(entity: schema.Entity, deep:bool = True) -> list[Contact]: + + """ + Will parse contacts from an entity + + Will return a list of contacts if it can be found, otherwise an empty list + + vcard entriews that are considered: + + ["email", {}, "text", email] + ["tel", {}, "text", phone] + ["fn", {}, "text", name] + + A contact is considered if `fn` and either of `email` or `tel` is present + + If deep is set to True, will also check nessted entities. + """ + + contacts = [] + + for vcard in entity.vcardArray[1:]: + + contact = Contact( + name = "", + roles = getattr(entity, "roles", []) or [] + ) + for vcard_entry in vcard: + if vcard_entry[0] == "fn": + contact.name = vcard_entry[3] + if vcard_entry[0] == "email": + contact.email = vcard_entry[3] + if vcard_entry[0] == "tel": + contact.phone = vcard_entry[3] + + if contact.name and (contact.email or contact.phone): + contacts.append(contact) + + if deep and getattr(entity, "entities", None): + for _entity in entity.entities: + contacts.extend(contacts_from_entity(_entity, deep=True)) + + # remove dupes + contacts = list(set(contacts)) + + recurse_contacts(entity, contacts, entity.roles) + + return contacts + +def contacts(entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: + + """ + Will parse contacts from an object + + Will return a list of contacts if it can be found, otherwise an empty list + + If deep is set to True, will also check nessted entities. + """ + + contacts = [] + + for _entity in entity.entities: + contacts.extend(contacts_from_entity(_entity, deep=deep)) + + # remove dupes + contacts = list(set(contacts)) + + # now cycle through and combine contacts + # + # check `name`, `phone` and `email` and if all three + # are the same, combine the roles + + combined_contacts = {} + + for _contact in contacts: + key = f"{_contact.name}-{_contact.email}-{_contact.phone}" + + if key in combined_contacts: + combined_contacts[key].roles.extend(_contact.roles) + else: + combined_contacts[key] = _contact + + contacts = list(combined_contacts.values()) + + return contacts + +def recurse_contacts(entity: schema.Entity, contacts: list[Contact], roles: list[str]) -> list[Contact]: + + request_state: RdapRequestState = rdap_request.get() + client = request_state.client + + # if role is in settings to recurse, try to do a lookup + if entity.handle and client: + + handle_url = entity.self_link + if not handle_url: + handle_url = client.get_entity_url(entity.handle) + + if not client.recurse_roles.isdisjoint(roles): + try: + with RdapRequestContext(url=handle_url, client=client): + r_entity = client.get_entity(entity.handle) + r_entity = r_entity.normalized + contacts.extend([ + Contact(**contact) for contact in r_entity["contacts"] + ]) + # check for HTTP Errors to ignore + except RdapHTTPError: + pass + + +def org_name_from_entity(entity: schema.Entity) -> str | None: + """ + Will parse an org name from an entity + + Will return the org name if it can be found, otherwise None + """ + # org name will be through entities + # looking for vcardArray with ["kind", {}, "text", org] to + # establish the org entry + # + # in the same vcardArray, looking for ["fn", {}, "text", name] + + kind_is_org = False + org_name = None + + for vcard in entity.vcardArray: + if vcard[0] == "kind" and vcard[3] == "org": + kind_is_org = True + if vcard[0] == "fn": + org_name = vcard[3] + + if kind_is_org and org_name: + return org_name + + return None + +def org_name(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an org name from an object + + Will return the org name if it can be found, if no org name + can be found, will return entity name or None + """ + + for _entity in entity.entities: + name = org_name_from_entity(_entity) + if name: + return name + + return entity.name or None + + +def prefix(ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network: + """ + Will return the CIDR of an IPNetwork object + "cidr0_cidrs" : [ { + "v4prefix" : "206.41.110.0", + "length" : 24 + } ], + """ + + cidr = ip_network.cidr0_cidrs[0] + + if "v4prefix" in cidr: + return ipaddress.IPv4Network(f"{cidr['v4prefix']}/{cidr['length']}") + + if "v6prefix" in cidr: + return ipaddress.IPv6Network(f"{cidr['v6prefix']}/{cidr['length']}") + + return None + +def parent_prefix(ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: + """ + Parent network prefix from `parentHandle` + + "parentHandle" : "NET-206-0-0-0-0", + """ + + if not ip_network.parentHandle: + return None + + # Extract the IP address part from the parentHandle + ip_parts = ip_network.parentHandle.split('-')[1:-1] + + # Reconstruct the IP address string + ip_str = '.'.join(ip_parts) + + # Determine the appropriate prefix length based on the number of non-zero octets + non_zero_octets = sum(1 for part in ip_parts if part != '0') + prefix_length = non_zero_octets * 8 + + # Construct the CIDR notation + cidr = f"{ip_str}/{prefix_length}" + + try: + # Attempt to create an IP network object + return ipaddress.ip_network(cidr, strict=False) + except ValueError: + # If the IP address is invalid, return None + return None + + +def secure_dns(domain: schema.Domain) -> DNSSEC: + if not domain.secureDNS: + return DNSSEC.unknown + + if domain.secureDNS.delegationSigned is None and domain.secureDNS.zeroSigned is None: + return DNSSEC.unknown + + if domain.secureDNS.delegationSigned or domain.secureDNS.zeroSigned: + return DNSSEC.secure + + return DNSSEC.insecure + + +def nameservers(domain: schema.Domain) -> list[Nameserver]: + nameservers = [] + + for nameserver in domain.nameservers: + nameservers.append(Nameserver(host=nameserver.ldhName)) + + return nameservers \ No newline at end of file diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index f26cc28..de298c7 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -20,7 +20,14 @@ class Timeout(Exception): class NotFound(KeyError): pass +class GoogleKeyNotSet(Exception): + pass + def get_client(key:str = GOOGLE_MAPS_API_KEY): + + if not key: + raise GoogleKeyNotSet("Google Maps API Key not set") + return googlemaps.Client(key) @@ -59,7 +66,17 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat Takes a formatted address and returns a normalized location object """ - result = lookup(formatted_address, client) + try: + result = lookup(formatted_address, client) + except GoogleKeyNotSet: + + # If a google maps key is not set, return a location object with + # only the address field set + + return Location( + updated = date or datetime.now(), + address = formatted_address, + ) city = None postal_code = None diff --git a/rdap/objects.py b/rdap/objects.py index 5f9cad2..a750d2e 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -207,20 +207,29 @@ def __init__(self, data, rdapc=None): super().__init__(data, rdapc) @property - def normalized(self): - normalize(self._data, self.get_rir(), "autnum") + def normalized(self) -> dict: + return normalize(self._data, self.get_rir(), "autnum") class RdapNetwork(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) - + @property + def normalized(self) -> dict: + return normalize(self._data, self.get_rir(), "ip") class RdapDomain(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) - + @property + def normalized(self) -> dict: + return normalize(self._data, self.get_rir(), "domain") + class RdapEntity(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) + + @property + def normalized(self) -> dict: + return normalize(self._data, self.get_rir(), "entity") \ No newline at end of file diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 4f91b90..f554154 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -44,7 +44,9 @@ class ROLE(str, enum.Enum): registrant = "registrant" NORMALIZED_ROLES = { - "administrative": "admin" + "administrative": "admin", + "noc": "technical", + "registrar": "registrant" } class DNSSEC(str, enum.Enum): @@ -64,10 +66,10 @@ class Location(pydantic.BaseModel): Describes a location """ updated: datetime - country: str - city: str - postal_code: str - address: str + country: str | None = None + city: str | None = None + postal_code: str | None = None + address: str | None = None geo: GeoLocation | None = None floor: str | None = None suite: str | None = None @@ -95,6 +97,10 @@ def normalize_roles(cls, data: Any) -> Any: data["roles"] = roles + # drop duplicates + + data["roles"] = list(set(data["roles"])) + return data @@ -156,8 +162,8 @@ class Entity(pydantic.BaseModel): created: datetime updated: datetime name: str - organization: Organization - location: Location + organization: Organization | None = None + location: Location | None = None contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) diff --git a/rdap/schema/arin.py b/rdap/schema/rdap.py similarity index 66% rename from rdap/schema/arin.py rename to rdap/schema/rdap.py index 2c1b784..a4851bf 100644 --- a/rdap/schema/arin.py +++ b/rdap/schema/rdap.py @@ -5,11 +5,11 @@ class Link(BaseModel): """Represents a hyperlink in the RDAP response.""" # The label or description of the link - value: str + value: str | None = None # The relationship of the link to the current object - rel: str + rel: str | None = None # The MIME type of the target resource - type: str + type: str | None = None # The URL of the link href: str @@ -27,7 +27,7 @@ class Notice(BaseModel): # A list of text lines comprising the notice description: list[str] # Optional links related to the notice - links: list[Link] | None = None + links: list[Link] = Field(default_factory=list) class VCardValue(BaseModel): """Represents additional properties for vCard values.""" @@ -44,25 +44,34 @@ class Remark(BaseModel): class Entity(BaseModel): """Represents an entity (organization, individual, or role) in the RDAP response.""" # A unique identifier for the entity - handle: str + handle: str = Field(default_factory=str) # Contact information in vCard format vcardArray: list[str | list[list[str | dict | list]]] = Field(default_factory=list) # Roles of the entity (e.g., registrant, technical, administrative) - roles: list[str] + roles: list[str] = Field(default_factory=list) # Links related to the entity - links: list[Link] + links: list[Link] = Field(default_factory=list) # Events associated with the entity - events: list[Event] + events: list[Event] = Field(default_factory=list) # Status of the entity - status: list[str] | None = None + status: list[str] = Field(default_factory=list) # WHOIS server for the entity - port43: str + port43: str = Field(default_factory=str) # Type of the object (always "entity" for Entity) objectClassName: str # Additional remarks about the entity - remarks: list[Remark] | None = None + remarks: list[Remark] = Field(default_factory=list) # Nested entities (e.g., contacts within an organization) - entities: list['Entity'] | None = None + entities: list['Entity'] = Field(default_factory=list) + + + @property + def self_link(self) -> str | None: + """Returns the href of the link where rel == 'self'""" + for link in self.links: + if link.rel == 'self': + return link.href + return None class IPNetwork(BaseModel): @@ -86,13 +95,13 @@ class IPNetwork(BaseModel): # Handle of the parent network parentHandle: str # Additional remarks about the network - remarks: list[Remark] | None = None + remarks: list[Remark] = Field(default_factory=list) # Events associated with the network - events: list[Event] + events: list[Event]= Field(default_factory=list) # Links related to the network - links: list[Link] + links: list[Link]= Field(default_factory=list) # Entities associated with the network - entities: list[Entity] + entities: list[Entity]= Field(default_factory=list) # WHOIS server for the network port43: str # Status of the network @@ -100,9 +109,37 @@ class IPNetwork(BaseModel): # Type of the object (always "ip network" for IPNetwork) objectClassName: str # CIDR notation for the network - cidr0_cidrs: list[dict] + cidr0_cidrs: list[dict]= Field(default_factory=list) # Origin AS numbers for the network - arin_originas0_originautnums: list + arin_originas0_originautnums: list = Field(default_factory=list) + +class DSData(BaseModel): + """Represents DS data for secure DNS in the RDAP response.""" + # Key tag for the DS record + keyTag: int + # Algorithm number for the DS record + algorithm: int + # Digest type for the DS record + digestType: int + # Digest value for the DS record + digest: str + +class SecureDNS(BaseModel): + # true if there are DS records in the parent, false otherwise. + delegationSigned: bool | None = None + # if the zone has been signed, false otherwise. + zeroSigned: bool | None = None + # DS data for secure DNS + dsData: list[DSData] = Field(default_factory=list) + +class Nameserver(BaseModel): + objectClassName: str + ldhName: str + unicodeName: str | None = None + ipAddresses: dict[str, list[str]] = Field(default_factory=dict) + remarks: list[Remark] = Field(default_factory=list) + port43: str | None = None + events: list[Event] = Field(default_factory=list) class Domain(BaseModel): """Represents a domain name in the RDAP response.""" @@ -115,18 +152,22 @@ class Domain(BaseModel): # The domain name in LDH (Letter Digit Hyphen) format ldhName: str # Events associated with the domain - events: list[Event] + events: list[Event] = Field(default_factory=list) # Links related to the domain - links: list[Link] + links: list[Link] = Field(default_factory=list) # Entities associated with the domain - entities: list[Entity] + entities: list[Entity] = Field(default_factory=list) # WHOIS server for the domain - port43: str + port43: str = Field(default_factory=str) # Network information for the domain - network: IPNetwork + network: IPNetwork | None = None # Type of the object (always "domain" for Domain) objectClassName: str + secureDNS: SecureDNS | None = None + + nameservers: list[Nameserver] = Field(default_factory=list) + class AutNum(BaseModel): """Represents an Autonomous System Number in the RDAP response.""" # list of conformance levels diff --git a/rdap/schema/source.py b/rdap/schema/source.py index 63cb762..4ad1d57 100644 --- a/rdap/schema/source.py +++ b/rdap/schema/source.py @@ -1,4 +1,4 @@ -import rdap.schema.arin as arin +import rdap.schema.rdap as rdap __all__ = [ "SCHEMAS_BY_RIR", @@ -9,7 +9,12 @@ ] SCHEMAS_BY_RIR = { - "arin": arin, + "arin": rdap, + "ripe": rdap, + "apnic": rdap, + "afrinic": rdap, + "lacnic": rdap, + None: rdap, } def ip_network_model(rir:str): From 347b8f386a0d6b443609410511702705e2808049 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Tue, 16 Jul 2024 17:26:24 +0000 Subject: [PATCH 12/28] comments --- rdap/normalize/base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 4529c2f..149e0cf 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -167,6 +167,8 @@ def recurse_contacts(entity: schema.Entity, contacts: list[Contact], roles: list ]) # check for HTTP Errors to ignore except RdapHTTPError: + # TODO: Do we ever want to raise on broken links? + # Probably not. pass @@ -263,6 +265,18 @@ def parent_prefix(ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipadd def secure_dns(domain: schema.Domain) -> DNSSEC: + """ + Will determine if the domain has secure DNS + + This is determined by the `secureDNS` object + + If `delegationSigned` or `zeroSigned` is True, will return secure + + If both are False, will return insecure + + If secureDNS is not present, or both are None, will return unknown + """ + if not domain.secureDNS: return DNSSEC.unknown @@ -276,6 +290,10 @@ def secure_dns(domain: schema.Domain) -> DNSSEC: def nameservers(domain: schema.Domain) -> list[Nameserver]: + """ + Returns normalized nameservers from a domain object + """ + nameservers = [] for nameserver in domain.nameservers: From bd54d4c76a6ce9061cad031641e3084288964030 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Wed, 17 Jul 2024 15:05:18 +0000 Subject: [PATCH 13/28] finalize normalization --- poetry.lock | 132 +++++----- pyproject.toml | 1 + rdap/context.py | 10 +- rdap/normalize/__init__.py | 61 +++-- rdap/normalize/afrinic.py | 15 ++ rdap/normalize/apnic.py | 35 +++ rdap/normalize/arin.py | 15 ++ rdap/normalize/base.py | 484 +++++++++++++++++++++---------------- rdap/normalize/geo.py | 13 +- rdap/normalize/lacnic.py | 15 ++ rdap/normalize/ripe.py | 32 +++ rdap/schema/normalized.py | 32 +-- rdap/schema/rdap.py | 21 +- 13 files changed, 542 insertions(+), 324 deletions(-) create mode 100644 rdap/normalize/afrinic.py create mode 100644 rdap/normalize/apnic.py create mode 100644 rdap/normalize/arin.py create mode 100644 rdap/normalize/lacnic.py create mode 100644 rdap/normalize/ripe.py diff --git a/poetry.lock b/poetry.lock index f4c7316..7661bc5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -64,14 +64,14 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cachetools" -version = "5.3.3" +version = "5.4.0" description = "Extensible memoizing collections and decorators" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, ] [[package]] @@ -239,64 +239,64 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.6.0" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.dependencies] @@ -331,14 +331,14 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -801,6 +801,18 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "phonenumbers" +version = "8.13.40" +description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "phonenumbers-8.13.40-py2.py3-none-any.whl", hash = "sha256:9582752c20a1da5ec4449f7f97542bf8a793c8e2fec0ab57f767177bb8fc0b1d"}, + {file = "phonenumbers-8.13.40.tar.gz", hash = "sha256:f137c2848b8e83dd064b71881b65680584417efa202177fd330e2f7ff6c68113"}, +] + [[package]] name = "platformdirs" version = "4.2.2" @@ -1441,4 +1453,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b732318e919c5196170885f6cac2a604d2d536436caa9b558ad70aad94533491" +content-hash = "2a091ce786f0a25b4675f51141305dfa4573a527811f585d53fd75a69c398852" diff --git a/pyproject.toml b/pyproject.toml index 5d96e66..e726d09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ requests = ">=2.25.1" munge = {version = ">=1.3", extras = ["yaml"]} pydantic = ">=2.8.2" googlemaps = ">=4.10" +phonenumbers = ">=8.13.0" [tool.poetry.dev-dependencies] # testing diff --git a/rdap/context.py b/rdap/context.py index 2a027c9..a6b6d41 100644 --- a/rdap/context.py +++ b/rdap/context.py @@ -2,8 +2,6 @@ import pydantic from datetime import datetime -from rdap.schema.rdap import Entity, AutNum, IPNetwork, Domain - __all__ = [ "rdap_request", "RdapRequestContext", @@ -20,10 +18,10 @@ class RdapRequestState(pydantic.BaseModel): sources: list[RdapSource] = pydantic.Field(default_factory=list) client: object | None = None #RdapClient - def update_source(self, entity:Entity | AutNum | IPNetwork | Domain): - self.sources[-1].handle = entity.handle - self.sources[-1].created = entity.events[-1].eventDate - self.sources[-1].updated = entity.events[0].eventDate + def update_source(self, handle:str, created:datetime | None, updated:datetime | None): + self.sources[-1].handle = handle + self.sources[-1].created = created + self.sources[-1].updated = updated # context that holds the currently requested rdap url diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py index e0793c6..37680ee 100644 --- a/rdap/normalize/__init__.py +++ b/rdap/normalize/__init__.py @@ -5,9 +5,18 @@ import json import rdap.normalize.base as base +import rdap.normalize.afrinic as afrinic +import rdap.normalize.apnic as apnic +import rdap.normalize.arin as arin +import rdap.normalize.lacnic as lacnic +import rdap.normalize.ripe as ripe + import rdap.normalize.geo as geo import rdap.schema.normalized as schema + +import rdap.schema.rdap as rdap_schema + from rdap.schema.source import ( autnum_model, entity_model, @@ -20,16 +29,21 @@ __all__ = [ "normalize", + "normalize_autnum", + "normalize_entity", + "normalize_ip", + "normalize_domain", + "get_sources", ] HANDLERS = { - "arin": base, - "ripe": base, - "apnic": base, - "afrinic": base, - "lacnic": base, - # domains are weird. - None: base + "arin": arin.Handler(), + "ripe": ripe.Handler(), + "apnic": apnic.Handler(), + "afrinic": afrinic.Handler(), + "lacnic": lacnic.Handler(), + # other (verisign for domains etc.) + None: base.Handler() } def get_sources( @@ -90,19 +104,22 @@ def normalize_autnum(data: dict, rir: str) -> dict: rdap_autnum = autnum_model(rir)(**data) - current_rdap_request.update_source(rdap_autnum) + current_rdap_request.update_source( + rdap_autnum.handle, + **handler.dates(rdap_autnum.events) + ) org_name = handler.org_name(rdap_autnum) org = schema.Organization(name=org_name) address = handler.address(rdap_autnum) + net = schema.Network( - created = rdap_autnum.events[-1].eventDate, - updated = rdap_autnum.events[0].eventDate, name = rdap_autnum.name, organization = org, asn = rdap_autnum.startAutnum, contacts = handler.contacts(rdap_autnum), + **handler.dates(rdap_autnum.events) ) location = geo.normalize(address, net.updated) @@ -128,21 +145,23 @@ def normalize_ip(data: dict, rir: str) -> dict: rdap_ip_network = ip_network_model(rir)(**data) - current_rdap_request.update_source(rdap_ip_network) + current_rdap_request.update_source( + rdap_ip_network.handle, + **handler.dates(rdap_ip_network.events) + ) prefix = handler.prefix(rdap_ip_network) net = schema.IPNetwork( - created = rdap_ip_network.events[-1].eventDate, - updated = rdap_ip_network.events[0].eventDate, name = rdap_ip_network.name, prefix = prefix, parent= handler.parent_prefix(rdap_ip_network), - version = prefix.version, + version = handler.ip_version(rdap_ip_network), type = rdap_ip_network.type, # TODO: What happens if more than one status is in there? status = rdap_ip_network.status[0] if rdap_ip_network.status else None, contacts = handler.contacts(rdap_ip_network), + **handler.dates(rdap_ip_network.events) ) if current_rdap_request: @@ -164,16 +183,17 @@ def normalize_domain(data: dict, rir: str) -> dict: rdap_domain = domain_model(rir)(**data) - current_rdap_request.update_source(rdap_domain) + current_rdap_request.update_source( + rdap_domain.handle, **handler.dates(rdap_domain.events) + ) net = schema.Domain( - created = rdap_domain.events[-1].eventDate, - updated = rdap_domain.events[0].eventDate, name = rdap_domain.ldhName, handle = rdap_domain.handle, dns_sec = handler.secure_dns(rdap_domain), contacts = handler.contacts(rdap_domain), nameservers = handler.nameservers(rdap_domain), + **handler.dates(rdap_domain.events) ) if current_rdap_request: @@ -196,7 +216,9 @@ def normalize_entity(data: dict, rir: str) -> dict: rdap_entity = entity_model(rir)(**data) - current_rdap_request.update_source(rdap_entity) + current_rdap_request.update_source( + rdap_entity.handle, **handler.dates(rdap_entity.events) + ) org_name = handler.org_name_from_entity(rdap_entity) @@ -208,11 +230,10 @@ def normalize_entity(data: dict, rir: str) -> dict: address = handler.address_from_entity(rdap_entity) entity = schema.Entity( - created = rdap_entity.events[-1].eventDate, - updated = rdap_entity.events[0].eventDate, name = rdap_entity.handle, organization = org, contacts = handler.contacts_from_entity(rdap_entity), + **handler.dates(rdap_entity.events) ) location = geo.normalize(address, entity.updated) diff --git a/rdap/normalize/afrinic.py b/rdap/normalize/afrinic.py new file mode 100644 index 0000000..995a1e1 --- /dev/null +++ b/rdap/normalize/afrinic.py @@ -0,0 +1,15 @@ +""" +Some case specific normalization functions for AFRINIC data. +""" + +import rdap.normalize.base as base + +__all__ = [ + "Handler", +] + +class Handler(base.Handler): + """ + No known AFRINIC specific normalizations. + """ + pass \ No newline at end of file diff --git a/rdap/normalize/apnic.py b/rdap/normalize/apnic.py new file mode 100644 index 0000000..0feaca9 --- /dev/null +++ b/rdap/normalize/apnic.py @@ -0,0 +1,35 @@ +""" +Some case specific normalization functions for APNIC data. +""" + +import rdap.normalize.base as base +import rdap.schema.rdap as schema + +__all__ = [ + "Handler", +] + +class Handler(base.Handler): + + """ + APNIC sometimes puts org name into the remarks + """ + + def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + If super() return None or equal to entity.name try checking + remarks for an entry where title == "description" + + TODO: Sometimes description just contains an address and no org name + How to handle this? + """ + + org_name = super().org_name(entity) + + if org_name is None or org_name == entity.name: + for remark in entity.remarks: + if remark.title == "description": + org_name = remark.description[0] + break + + return org_name \ No newline at end of file diff --git a/rdap/normalize/arin.py b/rdap/normalize/arin.py new file mode 100644 index 0000000..43ceef1 --- /dev/null +++ b/rdap/normalize/arin.py @@ -0,0 +1,15 @@ +""" +Some case specific normalization functions for ARIN data. +""" + +import rdap.normalize.base as base + +__all__ = [ + "Handler", +] + +class Handler(base.Handler): + """ + No known ARIN specific normalizations. + """ + pass \ No newline at end of file diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 149e0cf..881f7da 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -3,8 +3,10 @@ """ import ipaddress -from rdap.exceptions import RdapHTTPError +import phonenumbers + +from rdap.exceptions import RdapHTTPError from rdap.schema.normalized import ( Contact, Nameserver, @@ -16,287 +18,345 @@ import rdap.schema.rdap as schema __all__ = [ - "org_name_from_entity", - "org_name", - "contacts_from_entity", - "contacts", - "address_from_entity", - "address", - "prefix", - "parent_prefix", - "secure_dns", - "nameservers", + "Handler", ] -def address_from_entity(entity: schema.Entity) -> str | None: - """ - Will parse an address from an entity +class Handler: - Will return the address if it can be found, otherwise None - """ - # address will be through entities - # looking for vcardArray with ["adr", {"label": address}, "text"] to - # establish the address entry + def address_from_entity(self, entity: schema.Entity) -> str | None: + """ + Will parse an address from an entity - for vcard in entity.vcardArray[1:]: - for vcard_entry in vcard: - if vcard_entry[0] == "adr": - return vcard_entry[1].get("label") + Will return the address if it can be found, otherwise None + """ + # address will be through entities + # looking for vcardArray with ["adr", {"label": address}, "text"] to + # establish the address entry - return None + for vcard in entity.vcardArray[1:]: + for vcard_entry in vcard: + if vcard_entry[0] == "adr": + return vcard_entry[1].get("label") -def address(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: - """ - Will parse an address from an object + return None - Will return the address if it can be found, if no address - can be found, will return None - """ + def address(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an address from an object - for _entity in entity.entities: - address = address_from_entity(_entity) - if address: - return address + Will return the address if it can be found, if no address + can be found, will return None + """ - return None + for _entity in entity.entities: + address = self.address_from_entity(_entity) + if address: + return address -def contacts_from_entity(entity: schema.Entity, deep:bool = True) -> list[Contact]: + return None - """ - Will parse contacts from an entity + def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[Contact]: - Will return a list of contacts if it can be found, otherwise an empty list + """ + Will parse contacts from an entity - vcard entriews that are considered: + Will return a list of contacts if it can be found, otherwise an empty list - ["email", {}, "text", email] - ["tel", {}, "text", phone] - ["fn", {}, "text", name] + vcard entriews that are considered: - A contact is considered if `fn` and either of `email` or `tel` is present + ["email", {}, "text", email] + ["tel", {}, "text", phone] + ["fn", {}, "text", name] - If deep is set to True, will also check nessted entities. - """ + A contact is considered if `fn` and either of `email` or `tel` is present - contacts = [] + If deep is set to True, will also check nessted entities. + """ - for vcard in entity.vcardArray[1:]: + contacts = [] - contact = Contact( - name = "", - roles = getattr(entity, "roles", []) or [] - ) - for vcard_entry in vcard: - if vcard_entry[0] == "fn": - contact.name = vcard_entry[3] - if vcard_entry[0] == "email": - contact.email = vcard_entry[3] - if vcard_entry[0] == "tel": - contact.phone = vcard_entry[3] + for vcard in entity.vcardArray[1:]: - if contact.name and (contact.email or contact.phone): - contacts.append(contact) + contact = Contact( + name = "", + roles = getattr(entity, "roles", []) or [] + ) + for vcard_entry in vcard: + if vcard_entry[0] == "fn": + contact.name = vcard_entry[3] + if vcard_entry[0] == "email": + contact.email = vcard_entry[3] + if vcard_entry[0] == "tel": + contact.phone = vcard_entry[3] + try: + phone_number = phonenumbers.parse(contact.phone, None) + contact.phone = phonenumbers.format_number( + phone_number, + phonenumbers.PhoneNumberFormat.INTERNATIONAL + ) + except phonenumbers.phonenumberutil.NumberParseException: + # TODO: setting to allow for invalid phone numbers? + contact.phone = None - if deep and getattr(entity, "entities", None): - for _entity in entity.entities: - contacts.extend(contacts_from_entity(_entity, deep=True)) + if contact.name and (contact.email or contact.phone): + contacts.append(contact) - # remove dupes - contacts = list(set(contacts)) + if deep and getattr(entity, "entities", None): + for _entity in entity.entities: + contacts.extend(self.contacts_from_entity(_entity, deep=True)) - recurse_contacts(entity, contacts, entity.roles) + # remove dupes + contacts = list(set(contacts)) - return contacts + self.recurse_contacts(entity, contacts, entity.roles) -def contacts(entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: + return contacts - """ - Will parse contacts from an object + def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: - Will return a list of contacts if it can be found, otherwise an empty list + """ + Will parse contacts from an object - If deep is set to True, will also check nessted entities. - """ + Will return a list of contacts if it can be found, otherwise an empty list - contacts = [] + If deep is set to True, will also check nessted entities. + """ - for _entity in entity.entities: - contacts.extend(contacts_from_entity(_entity, deep=deep)) + contacts = [] - # remove dupes - contacts = list(set(contacts)) + for _entity in entity.entities: + contacts.extend(self.contacts_from_entity(_entity, deep=deep)) - # now cycle through and combine contacts - # - # check `name`, `phone` and `email` and if all three - # are the same, combine the roles + # remove dupes + contacts = list(set(contacts)) - combined_contacts = {} + # now cycle through and combine contacts + # + # check `name`, `phone` and `email` and if all three + # are the same, combine the roles - for _contact in contacts: - key = f"{_contact.name}-{_contact.email}-{_contact.phone}" + combined_contacts = {} - if key in combined_contacts: - combined_contacts[key].roles.extend(_contact.roles) - else: - combined_contacts[key] = _contact + for _contact in contacts: + key = f"{_contact.name}" - contacts = list(combined_contacts.values()) + if key in combined_contacts: + combined_contacts[key].roles.extend(_contact.roles) + if not combined_contacts[key].email and _contact.email and _contact.phone == combined_contacts[key].phone: + combined_contacts[key].email = _contact.email + if not combined_contacts[key].phone and _contact.phone and _contact.email == combined_contacts[key].email: + combined_contacts[key].phone = _contact.phone + else: + combined_contacts[key] = _contact - return contacts + contacts = list(combined_contacts.values()) -def recurse_contacts(entity: schema.Entity, contacts: list[Contact], roles: list[str]) -> list[Contact]: - - request_state: RdapRequestState = rdap_request.get() - client = request_state.client + return contacts - # if role is in settings to recurse, try to do a lookup - if entity.handle and client: + def recurse_contacts(self, entity: schema.Entity, contacts: list[Contact], roles: list[str]) -> list[Contact]: + + request_state: RdapRequestState = rdap_request.get() + client = request_state.client + + # if role is in settings to recurse, try to do a lookup + if entity.handle and client: + + handle_url = entity.self_link + if not handle_url: + handle_url = client.get_entity_url(entity.handle) + + if not client.recurse_roles.isdisjoint(roles): + try: + with RdapRequestContext(url=handle_url, client=client): + r_entity = client.get_entity(entity.handle) + r_entity = r_entity.normalized + contacts.extend([ + Contact(**contact) for contact in r_entity["contacts"] + ]) + # check for HTTP Errors to ignore + except RdapHTTPError: + # TODO: Do we ever want to raise on broken links? + # Probably not. + pass + + + def org_name_from_entity(self, entity: schema.Entity) -> str | None: + """ + Will parse an org name from an entity + + Will return the org name if it can be found, otherwise None + """ + # org name will be through entities + # looking for vcardArray with ["kind", {}, "text", org] to + # establish the org entry + # + # in the same vcardArray, looking for ["fn", {}, "text", name] - handle_url = entity.self_link - if not handle_url: - handle_url = client.get_entity_url(entity.handle) + kind_is_org = False + org_name = None - if not client.recurse_roles.isdisjoint(roles): - try: - with RdapRequestContext(url=handle_url, client=client): - r_entity = client.get_entity(entity.handle) - r_entity = r_entity.normalized - contacts.extend([ - Contact(**contact) for contact in r_entity["contacts"] - ]) - # check for HTTP Errors to ignore - except RdapHTTPError: - # TODO: Do we ever want to raise on broken links? - # Probably not. - pass + for vcard in entity.vcardArray[1:]: + for vcard_entry in vcard: + if vcard_entry[0] == "kind" and vcard_entry[3] == "org": + kind_is_org = True + if vcard_entry[0] == "fn": + org_name = vcard_entry[3] + + if kind_is_org and org_name: + return org_name + return None -def org_name_from_entity(entity: schema.Entity) -> str | None: - """ - Will parse an org name from an entity + def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + Will parse an org name from an object - Will return the org name if it can be found, otherwise None - """ - # org name will be through entities - # looking for vcardArray with ["kind", {}, "text", org] to - # establish the org entry - # - # in the same vcardArray, looking for ["fn", {}, "text", name] + Will return the org name if it can be found, if no org name + can be found, will return entity name or None + """ - kind_is_org = False - org_name = None + for _entity in entity.entities: + name = self.org_name_from_entity(_entity) + if name: + return name - for vcard in entity.vcardArray: - if vcard[0] == "kind" and vcard[3] == "org": - kind_is_org = True - if vcard[0] == "fn": - org_name = vcard[3] - - if kind_is_org and org_name: - return org_name + return entity.name or None - return None -def org_name(entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: - """ - Will parse an org name from an object + def prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network: + """ + Will return the CIDR of an IPNetwork object + "cidr0_cidrs" : [ { + "v4prefix" : "206.41.110.0", + "length" : 24 + } ], + """ - Will return the org name if it can be found, if no org name - can be found, will return entity name or None - """ + if not ip_network.cidr0_cidrs: - for _entity in entity.entities: - name = org_name_from_entity(_entity) - if name: - return name + # try if handle can be coerced into a prefix - return entity.name or None + if ip_network.handle: + try: + return ipaddress.ip_network(ip_network.handle) + except ValueError: + pass + return None -def prefix(ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network: - """ - Will return the CIDR of an IPNetwork object - "cidr0_cidrs" : [ { - "v4prefix" : "206.41.110.0", - "length" : 24 - } ], - """ + cidr = ip_network.cidr0_cidrs[0] - cidr = ip_network.cidr0_cidrs[0] - - if "v4prefix" in cidr: - return ipaddress.IPv4Network(f"{cidr['v4prefix']}/{cidr['length']}") - - if "v6prefix" in cidr: - return ipaddress.IPv6Network(f"{cidr['v6prefix']}/{cidr['length']}") + if "v4prefix" in cidr: + return ipaddress.IPv4Network(f"{cidr['v4prefix']}/{cidr['length']}") + + if "v6prefix" in cidr: + return ipaddress.IPv6Network(f"{cidr['v6prefix']}/{cidr['length']}") - return None - -def parent_prefix(ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: - """ - Parent network prefix from `parentHandle` - - "parentHandle" : "NET-206-0-0-0-0", - """ - - if not ip_network.parentHandle: return None - # Extract the IP address part from the parentHandle - ip_parts = ip_network.parentHandle.split('-')[1:-1] - - # Reconstruct the IP address string - ip_str = '.'.join(ip_parts) - - # Determine the appropriate prefix length based on the number of non-zero octets - non_zero_octets = sum(1 for part in ip_parts if part != '0') - prefix_length = non_zero_octets * 8 - - # Construct the CIDR notation - cidr = f"{ip_str}/{prefix_length}" - - try: - # Attempt to create an IP network object - return ipaddress.ip_network(cidr, strict=False) - except ValueError: - # If the IP address is invalid, return None + def ip_version(self, ip_network: schema.IPNetwork) -> int | None: + """ + Will return the IP version of an IPNetwork object + """ + + prefix = self.prefix(ip_network) + + if prefix: + return prefix.version + + ip_version = ip_network.ipVersion + + if ip_version == "v4": + return 4 + if ip_version == "v6": + return 6 + return None + def parent_prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: + """ + Parent network prefix from `parentHandle` + + "parentHandle" : "NET-206-0-0-0-0", + """ + + if not ip_network.parentHandle: + return None + + # Extract the IP address part from the parentHandle + ip_parts = ip_network.parentHandle.split('-')[1:-1] + + # Reconstruct the IP address string + ip_str = '.'.join(ip_parts) + + # Determine the appropriate prefix length based on the number of non-zero octets + non_zero_octets = sum(1 for part in ip_parts if part != '0') + prefix_length = non_zero_octets * 8 + + # Construct the CIDR notation + cidr = f"{ip_str}/{prefix_length}" + + try: + # Attempt to create an IP network object + return ipaddress.ip_network(cidr, strict=False) + except ValueError: + # If the IP address is invalid, return None + return None + + + def secure_dns(self, domain: schema.Domain) -> DNSSEC: + """ + Will determine if the domain has secure DNS + + This is determined by the `secureDNS` object + + If `delegationSigned` or `zeroSigned` is True, will return secure + + If both are False, will return insecure + + If secureDNS is not present, or both are None, will return unknown + """ -def secure_dns(domain: schema.Domain) -> DNSSEC: - """ - Will determine if the domain has secure DNS + if not domain.secureDNS: + return DNSSEC.unknown - This is determined by the `secureDNS` object + if domain.secureDNS.delegationSigned is None and domain.secureDNS.zeroSigned is None: + return DNSSEC.unknown - If `delegationSigned` or `zeroSigned` is True, will return secure + if domain.secureDNS.delegationSigned or domain.secureDNS.zeroSigned: + return DNSSEC.secure - If both are False, will return insecure + return DNSSEC.insecure - If secureDNS is not present, or both are None, will return unknown - """ - if not domain.secureDNS: - return DNSSEC.unknown + def nameservers(self, domain: schema.Domain) -> list[Nameserver]: + """ + Returns normalized nameservers from a domain object + """ - if domain.secureDNS.delegationSigned is None and domain.secureDNS.zeroSigned is None: - return DNSSEC.unknown + nameservers = [] - if domain.secureDNS.delegationSigned or domain.secureDNS.zeroSigned: - return DNSSEC.secure + for nameserver in domain.nameservers: + nameservers.append(Nameserver(host=nameserver.ldhName)) - return DNSSEC.insecure + return nameservers + def dates(self, events: list[schema.Event]) -> dict[str, str]: -def nameservers(domain: schema.Domain) -> list[Nameserver]: - """ - Returns normalized nameservers from a domain object - """ + """ + Return the created and updated dates from the events + """ - nameservers = [] + created = None + updated = None - for nameserver in domain.nameservers: - nameservers.append(Nameserver(host=nameserver.ldhName)) + for event in events: + if event.eventAction == "registration": + created = event.eventDate + if event.eventAction == "last changed": + updated = event.eventDate - return nameservers \ No newline at end of file + return { + "created": created, + "updated": updated + } \ No newline at end of file diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index de298c7..04a1ce4 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -68,7 +68,7 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat try: result = lookup(formatted_address, client) - except GoogleKeyNotSet: + except (GoogleKeyNotSet, NotFound): # If a google maps key is not set, return a location object with # only the address field set @@ -85,19 +85,20 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat suite = None for component in result.get("address_components", []): - if "country" in component.get("types", []): + types = component.get("types", []) + if "country" in types: country = component.get("short_name") - if "postal_code" in component.get("types", []): + if "postal_code" in types: postal_code = component.get("long_name") - if "locality" in component.get("types", []): + if "locality" in types or "postal_town" in types: city = component.get("long_name") - if "floor" in component.get("types", []): + if "floor" in types: floor = component.get("long_name") - if "subpremise" in component.get("types", []): + if "subpremise" in types: suite = component.get("long_name") diff --git a/rdap/normalize/lacnic.py b/rdap/normalize/lacnic.py new file mode 100644 index 0000000..29e72b3 --- /dev/null +++ b/rdap/normalize/lacnic.py @@ -0,0 +1,15 @@ +""" +Some case specific normalization functions for LACNIC data. +""" + +import rdap.normalize.base as base + +__all__ = [ + "Handler", +] + +class Handler(base.Handler): + """ + No known LACNIC specific normalizations. + """ + pass \ No newline at end of file diff --git a/rdap/normalize/ripe.py b/rdap/normalize/ripe.py new file mode 100644 index 0000000..f9217c5 --- /dev/null +++ b/rdap/normalize/ripe.py @@ -0,0 +1,32 @@ +""" +Some case specific normalization functions for RIPE data. +""" + +import rdap.normalize.base as base +import rdap.schema.rdap as schema + +__all__ = [ + "Handler", +] + +class Handler(base.Handler): + + """ + RIPE sometimes puts org name into the remarks + """ + + def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + """ + If super() return None or equal to entity.name try checking + remarks for an entry where title == "description" + """ + + org_name = super().org_name(entity) + + if org_name is None or org_name == entity.name: + for remark in entity.remarks: + if remark.title == "description": + org_name = remark.description[0] + break + + return org_name \ No newline at end of file diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index f554154..212a6a8 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -65,7 +65,7 @@ class Location(pydantic.BaseModel): """ Describes a location """ - updated: datetime + updated: datetime | None = None country: str | None = None city: str | None = None postal_code: str | None = None @@ -78,8 +78,8 @@ class Contact(pydantic.BaseModel): """ Describes a point of contact """ - #created: datetime - #updated: datetime + #created: datetime | None = None + #updated: datetime | None = None name: str roles: list[ROLE] = pydantic.Field(default_factory=list) phone: str | None = None @@ -113,10 +113,10 @@ class Source(pydantic.BaseModel): Will contain where the data was fetched from and when """ - created: datetime - updated: datetime + created: datetime | None = None + updated: datetime | None = None handle: str - urls: list[str] + urls: list[str] = pydantic.Field(default_factory=list) description: str | None = None @@ -130,8 +130,8 @@ class Network(pydantic.BaseModel): """ Describes a network """ - created: datetime - updated: datetime + created: datetime | None = None + updated: datetime | None = None asn: int name: str organization: Organization @@ -144,10 +144,10 @@ class IPNetwork(pydantic.BaseModel): """ Describes an IP network """ - created: datetime - updated: datetime - prefix: ipaddress.IPv4Network | ipaddress.IPv6Network - version: IP_VERSION + created: datetime | None = None + updated: datetime | None = None + prefix: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None + version: IP_VERSION | None = None name: str type: str status: STATUS @@ -159,8 +159,8 @@ class Entity(pydantic.BaseModel): """ Describes an entity """ - created: datetime - updated: datetime + created: datetime | None = None + updated: datetime | None = None name: str organization: Organization | None = None location: Location | None = None @@ -177,8 +177,8 @@ class Domain(pydantic.BaseModel): """ Describes a domain """ - created: datetime - updated: datetime + created: datetime | None = None + updated: datetime | None = None name: str handle: str dns_sec: DNSSEC diff --git a/rdap/schema/rdap.py b/rdap/schema/rdap.py index a4851bf..7c231e1 100644 --- a/rdap/schema/rdap.py +++ b/rdap/schema/rdap.py @@ -2,6 +2,17 @@ from datetime import datetime from typing import Any +__all__ = [ + 'Link', + 'Event', + 'Notice', + 'VCardValue', + 'Remark', + 'Entity', + 'IPNetwork', + +] + class Link(BaseModel): """Represents a hyperlink in the RDAP response.""" # The label or description of the link @@ -37,9 +48,9 @@ class VCardValue(BaseModel): class Remark(BaseModel): """Represents a remark or comment in the RDAP response.""" # The title of the remark - title: str + title: str | None = None # A list of text lines comprising the remark - description: list[str] + description: list[str] = Field(default_factory=list) class Entity(BaseModel): """Represents an entity (organization, individual, or role) in the RDAP response.""" @@ -93,7 +104,7 @@ class IPNetwork(BaseModel): # Type of the network allocation type: str # Handle of the parent network - parentHandle: str + parentHandle: str | None = None # Additional remarks about the network remarks: list[Remark] = Field(default_factory=list) # Events associated with the network @@ -196,6 +207,8 @@ class AutNum(BaseModel): entities: list[Entity] = Field(default_factory=list) # Status of the AS number status: list[str] = Field(default_factory=list) - + + # Remarks about the AS number + remarks: list[Remark] = Field(default_factory=list) Entity.model_rebuild() \ No newline at end of file From 54571780d238db4e41d5ad1c3fdfd6f0f56e3066 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Wed, 17 Jul 2024 17:57:18 +0000 Subject: [PATCH 14/28] some normalization and schema fixes --- rdap/normalize/base.py | 3 ++- rdap/schema/normalized.py | 22 +++++++++++++++++++--- rdap/schema/rdap.py | 16 ++++++++-------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 881f7da..8f382a5 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -79,7 +79,8 @@ def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[ contact = Contact( name = "", - roles = getattr(entity, "roles", []) or [] + roles = getattr(entity, "roles", []) or [], + **self.dates(entity.events) ) for vcard_entry in vcard: if vcard_entry[0] == "fn": diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 212a6a8..fa8c2b3 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -36,6 +36,11 @@ class STATUS(str, enum.Enum): active = "active" inactive = "inactive" +NORMALIZED_STATUS = { + "administrative": "active", + "validated": "active", +} + class ROLE(str, enum.Enum): abuse = "abuse" admin = "admin" @@ -78,8 +83,8 @@ class Contact(pydantic.BaseModel): """ Describes a point of contact """ - #created: datetime | None = None - #updated: datetime | None = None + created: datetime | None = None + updated: datetime | None = None name: str roles: list[ROLE] = pydantic.Field(default_factory=list) phone: str | None = None @@ -149,12 +154,23 @@ class IPNetwork(pydantic.BaseModel): prefix: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None version: IP_VERSION | None = None name: str - type: str + type: str | None = None status: STATUS parent: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) + @pydantic.model_validator(mode="before") + @classmethod + def normalize_status(cls, data: Any) -> Any: + + status = data.get("status") + + if status: + data["status"] = NORMALIZED_STATUS.get(status, status) + + return data + class Entity(pydantic.BaseModel): """ Describes an entity diff --git a/rdap/schema/rdap.py b/rdap/schema/rdap.py index 7c231e1..d8ee7c8 100644 --- a/rdap/schema/rdap.py +++ b/rdap/schema/rdap.py @@ -92,17 +92,17 @@ class IPNetwork(BaseModel): # Notices related to the IP network notices: list[Notice] = Field(default_factory=list) # A unique identifier for the IP network - handle: str + handle: str | None = None # The first IP address in the network range - startAddress: str + startAddress: str | None = None # The last IP address in the network range - endAddress: str + endAddress: str | None = None # IP version (v4 or v6) - ipVersion: str + ipVersion: str | None = None # Name of the network - name: str + name: str | None = None # Type of the network allocation - type: str + type: str | None = None # Handle of the parent network parentHandle: str | None = None # Additional remarks about the network @@ -114,9 +114,9 @@ class IPNetwork(BaseModel): # Entities associated with the network entities: list[Entity]= Field(default_factory=list) # WHOIS server for the network - port43: str + port43: str | None = None # Status of the network - status: list[str] + status: list[str] = Field(default_factory=list) # Type of the object (always "ip network" for IPNetwork) objectClassName: str # CIDR notation for the network From 12c661dc9b558d309df6ff69680b7e7feefcbebc Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 18 Jul 2024 16:31:10 +0000 Subject: [PATCH 15/28] nothing is required, apparently --- rdap/schema/normalized.py | 2 +- rdap/schema/rdap.py | 42 +++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index fa8c2b3..72e3e1f 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -153,7 +153,7 @@ class IPNetwork(pydantic.BaseModel): updated: datetime | None = None prefix: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None version: IP_VERSION | None = None - name: str + name: str | None = None type: str | None = None status: STATUS parent: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None diff --git a/rdap/schema/rdap.py b/rdap/schema/rdap.py index d8ee7c8..a65ac19 100644 --- a/rdap/schema/rdap.py +++ b/rdap/schema/rdap.py @@ -22,21 +22,21 @@ class Link(BaseModel): # The MIME type of the target resource type: str | None = None # The URL of the link - href: str + href: str | None = None class Event(BaseModel): """Represents a timestamped event in the lifecycle of an RDAP object.""" # The type of event (e.g., "registration", "last changed") - eventAction: str + eventAction: str | None = None # The date and time of the event - eventDate: datetime + eventDate: datetime | None = None class Notice(BaseModel): """Represents a notice or message in the RDAP response.""" # The title of the notice - title: str + title: str | None = None # A list of text lines comprising the notice - description: list[str] + description: list[str] = Field(default_factory=list) # Optional links related to the notice links: list[Link] = Field(default_factory=list) @@ -118,7 +118,7 @@ class IPNetwork(BaseModel): # Status of the network status: list[str] = Field(default_factory=list) # Type of the object (always "ip network" for IPNetwork) - objectClassName: str + objectClassName: str | None = None # CIDR notation for the network cidr0_cidrs: list[dict]= Field(default_factory=list) # Origin AS numbers for the network @@ -127,13 +127,13 @@ class IPNetwork(BaseModel): class DSData(BaseModel): """Represents DS data for secure DNS in the RDAP response.""" # Key tag for the DS record - keyTag: int + keyTag: int | None = None # Algorithm number for the DS record - algorithm: int + algorithm: int | None = None # Digest type for the DS record - digestType: int + digestType: int | None = None # Digest value for the DS record - digest: str + digest: str | None = None class SecureDNS(BaseModel): # true if there are DS records in the parent, false otherwise. @@ -144,8 +144,8 @@ class SecureDNS(BaseModel): dsData: list[DSData] = Field(default_factory=list) class Nameserver(BaseModel): - objectClassName: str - ldhName: str + objectClassName: str | None = None + ldhName: str | None = None unicodeName: str | None = None ipAddresses: dict[str, list[str]] = Field(default_factory=dict) remarks: list[Remark] = Field(default_factory=list) @@ -159,9 +159,9 @@ class Domain(BaseModel): # Notices related to the domain notices: list[Notice] = Field(default_factory=list) # A unique identifier for the domain - handle: str + handle: str | None = None # The domain name in LDH (Letter Digit Hyphen) format - ldhName: str + ldhName: str | None = None # Events associated with the domain events: list[Event] = Field(default_factory=list) # Links related to the domain @@ -173,7 +173,7 @@ class Domain(BaseModel): # Network information for the domain network: IPNetwork | None = None # Type of the object (always "domain" for Domain) - objectClassName: str + objectClassName: str | None = None secureDNS: SecureDNS | None = None @@ -186,18 +186,18 @@ class AutNum(BaseModel): # Notices related to the AS number notices: list[Notice] = Field(default_factory=list) # A unique identifier for the AS number - handle: str + handle: str | None = None # The starting AS number in the range - startAutnum: int + startAutnum: int | None = None # The ending AS number in the range (same as startAutnum for single AS) - endAutnum: int + endAutnum: int | None = None # Name of the AS - name: str + name: str | None = None # WHOIS server for the AS number - port43: str + port43: str | None = None # Type of the object (always "autnum" for AutNum) - objectClassName: str + objectClassName: str | None = None # Events associated with the AS number events: list[Event] = Field(default_factory=list) From 204b25e5beda116dd2448de246ffbb379862f40f Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Mon, 22 Jul 2024 16:46:35 +0000 Subject: [PATCH 16/28] support multiple lcoations and cache repeated requests in the context, both for rdap requests and geo lookups --- rdap/context.py | 25 +++++++++++++- rdap/normalize/__init__.py | 13 ++----- rdap/normalize/base.py | 69 +++++++++++++++++++++++++------------- rdap/normalize/geo.py | 16 +++++++-- rdap/schema/normalized.py | 7 ++-- 5 files changed, 90 insertions(+), 40 deletions(-) diff --git a/rdap/context.py b/rdap/context.py index a6b6d41..0c6c887 100644 --- a/rdap/context.py +++ b/rdap/context.py @@ -1,6 +1,7 @@ from contextvars import ContextVar import pydantic from datetime import datetime +from rdap.exceptions import RdapHTTPError __all__ = [ "rdap_request", @@ -18,6 +19,8 @@ class RdapRequestState(pydantic.BaseModel): sources: list[RdapSource] = pydantic.Field(default_factory=list) client: object | None = None #RdapClient + entities: dict = pydantic.Field(default_factory=dict) + def update_source(self, handle:str, created:datetime | None, updated:datetime | None): self.sources[-1].handle = handle self.sources[-1].created = created @@ -60,4 +63,24 @@ def __exit__(self, *exc): def push_url(self, url:str): state = rdap_request.get() - state.sources[-1].urls.append(url) \ No newline at end of file + state.sources[-1].urls.append(url) + + def get(self, typ:str, handle:str): + state = rdap_request.get() + client = state.client + + if typ not in ["entity", "ip", "domain", "autnum"]: + raise ValueError(f"Invalid type: {typ}") + + if state.entities.get(handle): + return state.entities[handle] + try: + get = getattr(client, f"get_{typ}") + r_entity = get(handle).normalized + state.entities[handle] = r_entity + return r_entity + except RdapHTTPError: + state.entities[handle] = {} + return {} + + diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py index 37680ee..b433de0 100644 --- a/rdap/normalize/__init__.py +++ b/rdap/normalize/__init__.py @@ -111,7 +111,6 @@ def normalize_autnum(data: dict, rir: str) -> dict: org_name = handler.org_name(rdap_autnum) org = schema.Organization(name=org_name) - address = handler.address(rdap_autnum) net = schema.Network( @@ -119,13 +118,10 @@ def normalize_autnum(data: dict, rir: str) -> dict: organization = org, asn = rdap_autnum.startAutnum, contacts = handler.contacts(rdap_autnum), + locations = handler.locations(rdap_autnum), **handler.dates(rdap_autnum.events) ) - location = geo.normalize(address, net.updated) - if location: - net.location = location - if current_rdap_request: net.sources = get_sources(current_rdap_request, rdap_autnum.handle, net) @@ -227,19 +223,14 @@ def normalize_entity(data: dict, rir: str) -> dict: else: org = None - address = handler.address_from_entity(rdap_entity) - entity = schema.Entity( name = rdap_entity.handle, organization = org, contacts = handler.contacts_from_entity(rdap_entity), + locations = handler.locations_from_entity(rdap_entity), **handler.dates(rdap_entity.events) ) - location = geo.normalize(address, entity.updated) - if location: - entity.location = location - if current_rdap_request: entity.sources = get_sources(current_rdap_request, rdap_entity.handle, entity) diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 8f382a5..979d3c9 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -5,13 +5,15 @@ import ipaddress import phonenumbers - +from datetime import datetime from rdap.exceptions import RdapHTTPError from rdap.schema.normalized import ( Contact, + Location, Nameserver, DNSSEC ) +import rdap.normalize.geo as geo from rdap.context import rdap_request, RdapRequestState, RdapRequestContext @@ -23,7 +25,7 @@ class Handler: - def address_from_entity(self, entity: schema.Entity) -> str | None: + def locations_from_entity(self, entity: schema.Entity) -> list[str]: """ Will parse an address from an entity @@ -33,14 +35,30 @@ def address_from_entity(self, entity: schema.Entity) -> str | None: # looking for vcardArray with ["adr", {"label": address}, "text"] to # establish the address entry + locations = [] + + dates = self.dates(entity.events) + + updated = dates["updated"] + for vcard in entity.vcardArray[1:]: for vcard_entry in vcard: if vcard_entry[0] == "adr": - return vcard_entry[1].get("label") + address = vcard_entry[1].get("label") + locations.append(geo.normalize(address, updated)) - return None + if entity.entities: + for _entity in entity.entities: + locations.extend( + self.locations_from_entity(_entity) + ) - def address(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + # remove dupes + locations = list(set(locations)) + + return locations + + def locations(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> list[str]: """ Will parse an address from an object @@ -48,12 +66,15 @@ def address(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> s can be found, will return None """ + locations = [] + for _entity in entity.entities: - address = self.address_from_entity(_entity) - if address: - return address + locations.extend(self.locations_from_entity(_entity)) - return None + # remove dupes + locations = list(set(locations)) + + return locations def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[Contact]: @@ -167,18 +188,10 @@ def recurse_contacts(self, entity: schema.Entity, contacts: list[Contact], roles handle_url = client.get_entity_url(entity.handle) if not client.recurse_roles.isdisjoint(roles): - try: - with RdapRequestContext(url=handle_url, client=client): - r_entity = client.get_entity(entity.handle) - r_entity = r_entity.normalized - contacts.extend([ - Contact(**contact) for contact in r_entity["contacts"] - ]) - # check for HTTP Errors to ignore - except RdapHTTPError: - # TODO: Do we ever want to raise on broken links? - # Probably not. - pass + with RdapRequestContext(url=handle_url, client=client) as ctx: + contacts.extend([ + Contact(**contact) for contact in ctx.get("entity", entity.handle)["contacts"] + ]) def org_name_from_entity(self, entity: schema.Entity) -> str | None: @@ -353,9 +366,19 @@ def dates(self, events: list[schema.Event]) -> dict[str, str]: for event in events: if event.eventAction == "registration": - created = event.eventDate + created: datetime = event.eventDate if event.eventAction == "last changed": - updated = event.eventDate + updated: datetime = event.eventDate + + + # set utc timezone if no timezone is present + # created and du + + if created and not created.tzinfo: + created = created.replace(tzinfo=datetime.timezone.utc) + + if updated and not updated.tzinfo: + updated = updated.replace(tzinfo=datetime.timezone.utc) return { "created": created, diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index 04a1ce4..ca9a2d6 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -8,6 +8,7 @@ from datetime import datetime from rdap.schema.normalized import Location, GeoLocation +from rdap.context import rdap_request, RdapRequestState GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY") @@ -37,7 +38,12 @@ def lookup(formatted_address:str, client = None) -> dict: location. """ - # TODO: cache results + request: RdapRequestState = rdap_request.get() + + key = f"geo:{formatted_address}" + + if key in request.entities: + return request.entities[key] if not client: client = get_client() @@ -58,6 +64,10 @@ def lookup(formatted_address:str, client = None) -> dict: if not result: raise NotFound() + # cache to avoid duplicate lookups during the same + # request context + request.entities[key] = result[0] + return result[0] def normalize(formatted_address:str, date:datetime = None, client=None) -> Location: @@ -74,7 +84,7 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat # only the address field set return Location( - updated = date or datetime.now(), + updated = date, address = formatted_address, ) @@ -103,7 +113,7 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat return Location( - updated = date or datetime.now(), + updated = date, country = country, city = city, postal_code = postal_code, diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 72e3e1f..42798b3 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -79,6 +79,9 @@ class Location(pydantic.BaseModel): floor: str | None = None suite: str | None = None + def __hash__(self): + return f"{self.address}-{self.city}-{self.country}-{self.postal_code}-{self.floor}-{self.suite}".__hash__() + class Contact(pydantic.BaseModel): """ Describes a point of contact @@ -140,7 +143,7 @@ class Network(pydantic.BaseModel): asn: int name: str organization: Organization - location: Location | None = None + locations: list[Location] = pydantic.Field(default_factory=list) contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) @@ -179,7 +182,7 @@ class Entity(pydantic.BaseModel): updated: datetime | None = None name: str organization: Organization | None = None - location: Location | None = None + locations: list[Location] = pydantic.Field(default_factory=list) contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) From 898fd2f0adadb80edda138d5aaf37a9c3f0a5e07 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Wed, 24 Jul 2024 19:03:00 +0000 Subject: [PATCH 17/28] tests --- rdap/normalize/base.py | 11 + rdap/normalize/geo.py | 8 +- rdap/schema/rdap.py | 2 +- tests/data/iana/bootstrap/asn.json | 2 +- tests/data/normalize/autnum/63311.expected | 63 + tests/data/normalize/autnum/63311.input | 250 +++ tests/data/normalize/autnum/8283.expected | 307 ++++ tests/data/normalize/autnum/8283.input | 1468 +++++++++++++++++ tests/data/normalize/domain/20c.com.expected | 74 + tests/data/normalize/domain/20c.com.input | 31 + .../data/normalize/entity/CLUE1-RIPE.expected | 63 + tests/data/normalize/entity/CLUE1-RIPE.input | 85 + tests/data/normalize/entity/DJVG.expected | 73 + tests/data/normalize/entity/DJVG.input | 45 + tests/data/normalize/ip/206.41.110.0.expected | 116 ++ tests/data/normalize/ip/206.41.110.0.input | 584 +++++++ tests/data/rdap/domain/20c.com.input | 208 +++ tests/data/rdap/ip/206.41.110.0.input | 584 +++++++ tests/test_geo.py | 102 ++ tests/test_normalize.py | 65 + 20 files changed, 4137 insertions(+), 4 deletions(-) create mode 100644 tests/data/normalize/autnum/63311.expected create mode 100644 tests/data/normalize/autnum/63311.input create mode 100644 tests/data/normalize/autnum/8283.expected create mode 100644 tests/data/normalize/autnum/8283.input create mode 100644 tests/data/normalize/domain/20c.com.expected create mode 100644 tests/data/normalize/domain/20c.com.input create mode 100644 tests/data/normalize/entity/CLUE1-RIPE.expected create mode 100644 tests/data/normalize/entity/CLUE1-RIPE.input create mode 100644 tests/data/normalize/entity/DJVG.expected create mode 100644 tests/data/normalize/entity/DJVG.input create mode 100644 tests/data/normalize/ip/206.41.110.0.expected create mode 100644 tests/data/normalize/ip/206.41.110.0.input create mode 100644 tests/data/rdap/domain/20c.com.input create mode 100644 tests/data/rdap/ip/206.41.110.0.input create mode 100644 tests/test_geo.py create mode 100644 tests/test_normalize.py diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 979d3c9..7649a65 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -74,6 +74,9 @@ def locations(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> # remove dupes locations = list(set(locations)) + # sort by address + locations.sort(key=lambda x: x.address) + return locations def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[Contact]: @@ -159,6 +162,9 @@ def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, dee combined_contacts = {} + # sort by name and email + contacts = sorted(contacts, key=lambda x: (x.name, x.email)) + for _contact in contacts: key = f"{_contact.name}" @@ -171,8 +177,13 @@ def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, dee else: combined_contacts[key] = _contact + contacts = list(combined_contacts.values()) + # sort roles + for contact in contacts: + contact.roles = sorted(list(set(contact.roles))) + return contacts def recurse_contacts(self, entity: schema.Entity, contacts: list[Contact], roles: list[str]) -> list[Contact]: diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index ca9a2d6..8dbe6f2 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -78,11 +78,11 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat try: result = lookup(formatted_address, client) - except (GoogleKeyNotSet, NotFound): + except (GoogleKeyNotSet, NotFound) as exc: # If a google maps key is not set, return a location object with # only the address field set - + print("EXC", exc) return Location( updated = date, address = formatted_address, @@ -94,8 +94,12 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat floor = None suite = None + address_components = result.get("address_components", []) + print("Address components:", address_components) + for component in result.get("address_components", []): types = component.get("types", []) + print("Component:", component) if "country" in types: country = component.get("short_name") diff --git a/rdap/schema/rdap.py b/rdap/schema/rdap.py index a65ac19..60bfb5e 100644 --- a/rdap/schema/rdap.py +++ b/rdap/schema/rdap.py @@ -57,7 +57,7 @@ class Entity(BaseModel): # A unique identifier for the entity handle: str = Field(default_factory=str) # Contact information in vCard format - vcardArray: list[str | list[list[str | dict | list]]] = Field(default_factory=list) + vcardArray: list[str | list[list[str | dict | list | None]]] = Field(default_factory=list) # Roles of the entity (e.g., registrant, technical, administrative) roles: list[str] = Field(default_factory=list) # Links related to the entity diff --git a/tests/data/iana/bootstrap/asn.json b/tests/data/iana/bootstrap/asn.json index 60d047e..2b6a43b 100644 --- a/tests/data/iana/bootstrap/asn.json +++ b/tests/data/iana/bootstrap/asn.json @@ -1 +1 @@ -{"description":"RDAP bootstrap file for Autonomous System Number allocations","publication":"2023-08-10T17:00:03Z","services":[[["36864-37887","327680-328703","328704-329727"],["https://rdap.afrinic.net/rdap/","http://rdap.afrinic.net/rdap/"]],[["4608-4865","7467-7722","9216-10239","17408-18431","23552-24575","37888-38911","45056-46079","55296-56319","58368-59391","63488-63999","64000-64098","64297-64395","131072-132095","132096-133119","133120-133631","133632-134556","134557-135580","135581-136505","136506-137529","137530-138553","138554-139577","139578-140601","140602-141625","141626-142649","142650-143673","143674-144697","144698-145721","145722-146745","146746-147769","147770-148793","148794-149817","149818-150841","150842-151865","151866-152889","152890-153913"],["https://rdap.apnic.net/"]],[["1-1876","1902-2042","2044-2046","2048-2106","2137-2584","2615-2772","2823-2829","2880-3153","3354-4607","4866-5376","5632-6655","6912-7466","7723-8191","10240-12287","13312-15359","16384-17407","18432-20479","21504-23455","23457-23551","25600-26623","26624-27647","29696-30719","31744-32767","32768-33791","35840-36863","39936-40959","46080-47103","53248-54271","54272-55295","62464-63487","64198-64296","393216-394239","394240-395164","395165-396188","396189-397212","397213-398236","398237-399260","399261-400284","400285-401308"],["https://rdap.arin.net/registry/","http://rdap.arin.net/registry/"]],[["1877-1901","2043","2047","2107-2136","2585-2614","2773-2822","2830-2879","3154-3353","5377-5631","6656-6911","8192-9215","12288-13311","15360-16383","20480-21503","24576-25599","28672-29695","30720-31743","33792-34815","34816-35839","38912-39935","40960-41983","41984-43007","43008-44031","44032-45055","47104-48127","48128-49151","49152-50175","50176-51199","51200-52223","56320-57343","57344-58367","59392-60415","60416-61439","61952-62463","64396-64495","196608-197631","197632-198655","198656-199679","199680-200191","200192-201215","201216-202239","202240-203263","203264-204287","204288-205211","205212-206235","206236-207259","207260-208283","208284-209307","209308-210331","210332-211355","211356-212379","212380-213403","213404-214427","214428-215451","215452-216475"],["https://rdap.db.ripe.net/"]],[["27648-28671","52224-53247","61440-61951","64099-64197","262144-263167","263168-263679","263680-264604","264605-265628","265629-266652","266653-267676","267677-268700","268701-269724","269725-270748","270749-271772","271773-272796","272797-273820"],["https://rdap.lacnic.net/rdap/"]]],"version":"1.0"} \ No newline at end of file +{"description":"RDAP bootstrap file for Autonomous System Number allocations","publication":"2024-04-10T20:00:01Z","services":[[["36864-37887","327680-328703","328704-329727"],["https://rdap.afrinic.net/rdap/","http://rdap.afrinic.net/rdap/"]],[["4608-4865","7467-7722","9216-10239","17408-18431","23552-24575","37888-38911","45056-46079","55296-56319","58368-59391","63488-63999","64000-64098","64297-64395","131072-132095","132096-133119","133120-133631","133632-134556","134557-135580","135581-136505","136506-137529","137530-138553","138554-139577","139578-140601","140602-141625","141626-142649","142650-143673","143674-144697","144698-145721","145722-146745","146746-147769","147770-148793","148794-149817","149818-150841","150842-151865","151866-152889","152890-153913"],["https://rdap.apnic.net/"]],[["1-1876","1902-2042","2044-2046","2048-2106","2137-2584","2615-2772","2823-2829","2880-3153","3354-4607","4866-5376","5632-6655","6912-7466","7723-8191","10240-12287","13312-15359","16384-17407","18432-20479","21504-23455","23457-23551","25600-26623","26624-27647","29696-30719","31744-32767","32768-33791","35840-36863","39936-40959","46080-47103","53248-54271","54272-55295","62464-63487","64198-64296","393216-394239","394240-395164","395165-396188","396189-397212","397213-398236","398237-399260","399261-400284","400285-401308","401309-402332"],["https://rdap.arin.net/registry/","http://rdap.arin.net/registry/"]],[["1877-1901","2043","2047","2107-2136","2585-2614","2773-2822","2830-2879","3154-3353","5377-5631","6656-6911","8192-9215","12288-13311","15360-16383","20480-21503","24576-25599","28672-29695","30720-31743","33792-34815","34816-35839","38912-39935","40960-41983","41984-43007","43008-44031","44032-45055","47104-48127","48128-49151","49152-50175","50176-51199","51200-52223","56320-57343","57344-58367","59392-60415","60416-61439","61952-62463","64396-64495","196608-197631","197632-198655","198656-199679","199680-200191","200192-201215","201216-202239","202240-203263","203264-204287","204288-205211","205212-206235","206236-207259","207260-208283","208284-209307","209308-210331","210332-211355","211356-212379","212380-213403","213404-214427","214428-215451","215452-216475"],["https://rdap.db.ripe.net/"]],[["27648-28671","52224-53247","61440-61951","64099-64197","262144-263167","263168-263679","263680-264604","264605-265628","265629-266652","266653-267676","267677-268700","268701-269724","269725-270748","270749-271772","271773-272796","272797-273820","273821-274844"],["https://rdap.lacnic.net/rdap/"]]],"version":"1.0"} \ No newline at end of file diff --git a/tests/data/normalize/autnum/63311.expected b/tests/data/normalize/autnum/63311.expected new file mode 100644 index 0000000..d03582f --- /dev/null +++ b/tests/data/normalize/autnum/63311.expected @@ -0,0 +1,63 @@ +{ + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "asn": 63311, + "name": "20C", + "organization": { + "name": "20C, LLC" + }, + "locations": [ + { + "updated": "2014-08-05T15:21:11-04:00", + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "303 W Ohio #1701\nChicago\nIL\n60654\nUnited States", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": "2023-08-02T14:15:09-04:00", + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + } + ], + "contacts": [ + { + "created": "2014-07-03T23:22:49-04:00", + "updated": "2023-08-02T14:15:09-04:00", + "name": "Network Engineers", + "roles": [ + "abuse", + "admin", + "technical" + ], + "phone": "+1 978-636-0020", + "email": "neteng@20c.com" + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/autnum/63311.input b/tests/data/normalize/autnum/63311.input new file mode 100644 index 0000000..fa38ec9 --- /dev/null +++ b/tests/data/normalize/autnum/63311.input @@ -0,0 +1,250 @@ +{ + "rdapConformance": [ + "nro_rdap_profile_0", + "rdap_level_0", + "nro_rdap_profile_asn_flat_0" + ], + "notices": [ + { + "title": "Terms of Service", + "description": [ + "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "terms-of-service", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/tou/" + } + ] + }, + { + "title": "Whois Inaccuracy Reporting", + "description": [ + "If you see inaccuracies in the results, please visit: " + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "inaccuracy-report", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } + ] + }, + { + "title": "Copyright Notice", + "description": [ + "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." + ] + } + ], + "handle": "AS63311", + "startAutnum": 63311, + "endAutnum": 63311, + "name": "20C", + "events": [ + { + "eventAction": "last changed", + "eventDate": "2018-10-24T22:58:16-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-11-17T14:28:43-05:00" + } + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/autnum/63311" + }, + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/asn/AS63311" + } + ], + "entities": [ + { + "handle": "CL-672", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "20C, LLC" + ], + [ + "adr", + { + "label": "303 W Ohio #1701\nChicago\nIL\n60654\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "org" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/CL-672" + }, + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/org/CL-672" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2014-08-05T15:21:11-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-08-05T15:21:11-04:00" + } + ], + "entities": [ + { + "handle": "NETWO7047-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Network Engineers" + ], + [ + "org", + {}, + "text", + "Network Engineers" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "neteng@20c.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-978-636-0020" + ] + ] + ], + "roles": [ + "technical", + "abuse", + "administrative" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/NETWO7047-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/autnum/63311", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/NETWO7047-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2023-08-02T14:15:09-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-07-03T23:22:49-04:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity" + } + ], + "port43": "whois.arin.net", + "objectClassName": "entity" + } + ], + "port43": "whois.arin.net", + "status": [ + "active" + ], + "objectClassName": "autnum" +} \ No newline at end of file diff --git a/tests/data/normalize/autnum/8283.expected b/tests/data/normalize/autnum/8283.expected new file mode 100644 index 0000000..32f2c37 --- /dev/null +++ b/tests/data/normalize/autnum/8283.expected @@ -0,0 +1,307 @@ +{ + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "asn": 8283, + "name": "COLOCLUE-AS", + "organization": { + "name": "Netwerkvereniging Coloclue" + }, + "locations": [ + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Bijsterveld 1\n4902 ZN Oosterhout\nThe Netherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "BytePark\nGruttostraat 63\nNL-2665 EL Bleiswijk\nThe Netherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Frans Duwaerstraat 34\n1318 AC\nAlmere\nNETHERLANDS", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Frans Duwaerstraat 34\n1318 AC ALMERE\nThe Netherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Frans Duwaerstraat 34\n1318AC Almere\nNetherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Kanaalstraat 89-BS", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Kruier 1\n1567LA Assendelft\nNetherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Maasstraat 16\n1442RV Purmerend\nNetherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "NETHERLANDS", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Purmer 60, 8244AT Lelystad", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Wamberg 26-1\n1083CV\nAmsterdam\nNETHERLANDS", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + }, + { + "updated": null, + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Wandeling 8\n4301 JT Zierikzee\nNetherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + } + ], + "contacts": [ + { + "created": null, + "updated": null, + "name": "Jasper Backer", + "roles": [ + "technical" + ], + "phone": "+31 6 83708052", + "email": null + }, + { + "created": null, + "updated": null, + "name": "Jelle Luteijn", + "roles": [ + "admin", + "technical" + ], + "phone": null, + "email": "hostmaster@luje.net" + }, + { + "created": null, + "updated": null, + "name": "Jurrian van Iersel", + "roles": [ + "technical" + ], + "phone": "+31 85 876 8785", + "email": "ripe@jurrian.vaniersel.net" + }, + { + "created": null, + "updated": null, + "name": "Mark Scholten", + "roles": [ + "technical" + ], + "phone": "+31 6 42408602", + "email": null + }, + { + "created": null, + "updated": null, + "name": "Menno Wouter Thomas Spaans", + "roles": [ + "admin" + ], + "phone": "+31 6 24206858", + "email": "menno@someones.net" + }, + { + "created": null, + "updated": null, + "name": "Netwerkvereniging Coloclue", + "roles": [ + "abuse", + "admin", + "registrant", + "technical" + ], + "phone": "+31 6 51387718", + "email": "abuse@coloclue.net" + }, + { + "created": null, + "updated": null, + "name": "Niels Raijer", + "roles": [ + "technical" + ], + "phone": "+31 6 54918205", + "email": null + }, + { + "created": null, + "updated": null, + "name": "Paul de Weerd", + "roles": [ + "admin", + "technical" + ], + "phone": "+31 6 51387718", + "email": "weerd@weirdnet.nl" + }, + { + "created": null, + "updated": null, + "name": "Rogier Krieger", + "roles": [ + "technical" + ], + "phone": "+31 15 212 8820", + "email": "hostmaster@bytepark.net" + }, + { + "created": null, + "updated": null, + "name": "Tijn Buijs", + "roles": [ + "admin", + "technical" + ], + "phone": "+31 6 13962562", + "email": "contact@cybertinus.nl" + }, + { + "created": null, + "updated": null, + "name": "Tim de Boer", + "roles": [ + "technical" + ], + "phone": "+31 6 40282615", + "email": null + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + }, + { + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "handle": "AS8283", + "urls": [ + "https://rdap.org/autnum/8283", + "https://rdap.org/autnum/8283" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/autnum/8283.input b/tests/data/normalize/autnum/8283.input new file mode 100644 index 0000000..f39cdf0 --- /dev/null +++ b/tests/data/normalize/autnum/8283.input @@ -0,0 +1,1468 @@ +{ + "handle": "AS8283", + "startAutnum": 8283, + "endAutnum": 8283, + "name": "COLOCLUE-AS", + "status": [ + "active" + ], + "entities": [ + { + "handle": "CLUE1-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Netwerkvereniging Coloclue" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "adr", + { + "label": "Frans Duwaerstraat 34\n1318AC Almere\nNetherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31651387718" + ], + [ + "email", + { + "type": "email" + }, + "text", + "ops@coloclue.net" + ], + [ + "email", + { + "type": "email" + }, + "text", + "routers@coloclue.net" + ], + [ + "email", + { + "type": "abuse" + }, + "text", + "abuse@coloclue.net" + ] + ] + ], + "roles": [ + "technical", + "administrative" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/CLUE1-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "COLOCLUE-MNT", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "coloclue-mnt" + ], + [ + "kind", + {}, + "text", + "individual" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/COLOCLUE-MNT" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "ORG-NC22-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Netwerkvereniging Coloclue" + ], + [ + "kind", + {}, + "text", + "org" + ], + [ + "adr", + { + "label": "Frans Duwaerstraat 34\n1318 AC\nAlmere\nNETHERLANDS" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31651387718" + ], + [ + "email", + { + "type": "email" + }, + "text", + "ops@coloclue.net" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/ORG-NC22-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "RIPE-NCC-END-MNT", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "RIPE-NCC-END-MNT" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "org", + {}, + "text", + "ORG-NCC1-RIPE" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/RIPE-NCC-END-MNT" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "CLUE1-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Netwerkvereniging Coloclue" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "adr", + { + "label": "Frans Duwaerstraat 34\n1318AC Almere\nNetherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31651387718" + ], + [ + "email", + { + "type": "email" + }, + "text", + "ops@coloclue.net" + ], + [ + "email", + { + "type": "email" + }, + "text", + "routers@coloclue.net" + ], + [ + "email", + { + "type": "abuse" + }, + "text", + "abuse@coloclue.net" + ] + ] + ], + "roles": [ + "abuse" + ], + "entities": [ + { + "handle": "BCKR1-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Jasper Backer" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Kanaalstraat 89-BS" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31683708052" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/BCKR1-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "COLOCLUE-MNT", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "coloclue-mnt" + ], + [ + "kind", + {}, + "text", + "individual" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/COLOCLUE-MNT" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "JVI-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Jurrian van Iersel" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Wandeling 8\n4301 JT Zierikzee\nNetherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31 85 8768785" + ], + [ + "email", + { + "type": "email" + }, + "text", + "ripe@jurrian.vaniersel.net" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/JVI-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "LUJE-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Jelle Luteijn" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "NETHERLANDS" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31" + ], + [ + "email", + { + "type": "email" + }, + "text", + "hostmaster@luje.net" + ] + ] + ], + "roles": [ + "technical", + "administrative" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/LUJE-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "MS44437-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Mark Scholten" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Purmer 60, 8244AT Lelystad" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31(0)642408602" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/MS44437-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "MWTS1-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Menno Wouter Thomas Spaans" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Maasstraat 16\n1442RV Purmerend\nNetherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31 6 24206858" + ], + [ + "email", + { + "type": "email" + }, + "text", + "menno@someones.net" + ] + ] + ], + "roles": [ + "administrative" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/MWTS1-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "NMR5-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Niels Raijer" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Kruier 1\n1567LA Assendelft\nNetherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31 6 54918205" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/NMR5-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "PDW-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Paul de Weerd" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Frans Duwaerstraat 34\n1318 AC ALMERE\nThe Netherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31651387718" + ], + [ + "email", + { + "type": "email" + }, + "text", + "weerd@weirdnet.nl" + ] + ] + ], + "roles": [ + "technical", + "administrative" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/PDW-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "RAK24-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Rogier Krieger" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "BytePark\nGruttostraat 63\nNL-2665 EL Bleiswijk\nThe Netherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31 15 212 8820" + ], + [ + "email", + { + "type": "email" + }, + "text", + "hostmaster@bytepark.net" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/RAK24-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "TDB189-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Tim de Boer" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Wamberg 26-1\n1083CV\nAmsterdam\nNETHERLANDS" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31640282615" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/TDB189-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + }, + { + "handle": "TIJN-RIPE", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Tijn Buijs" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "adr", + { + "label": "Bijsterveld 1\n4902 ZN Oosterhout\nThe Netherlands" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "text", + "+31613962562" + ], + [ + "email", + { + "type": "email" + }, + "text", + "contact@cybertinus.nl" + ] + ] + ], + "roles": [ + "technical", + "administrative" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/entity/TIJN-RIPE" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "objectClassName": "entity" + } + ], + "objectClassName": "entity" + } + ], + "remarks": [ + { + "description": [ + "Netwerkvereniging Coloclue, Amsterdam, Netherlands", + "-----------------------------------------------------", + "Transit", + "Atom86", + "", + "True", + "Fiberring", + "Fusix Networks", + "Peering", + "AMS-IX Office", + "AMS-IX Route Servers", + "NL-IX Route Servers", + "OTEGLOBE", + "TalkTalk", + "Hurricane Electric", + "InterBox Internet", + "OpenCarrier", + "BIT", + "TNG", + "Opteamax", + "IP-Max SA", + "MaxiTEL", + "MCKAYCOM Ltd", + "Packet Clearing House", + "Liquid Telecom", + "MANDA", + "Colocenter.nl", + "Interconnect Services B.V.", + "OpenPeering", + "Solcon", + "Eurofiber", + "OpenDNS", + "Akamai", + "Previder", + "RIPE NCC RIS Project", + "Netflix", + "PT Comunicacoes S.A.", + "NTT", + "Not Surfnet", + "Breedband Nederland", + "Leaseweb", + "Init7", + "Apple Inc", + "Duocast", + "Iunxi", + "Tuxis Internet Engineering", + "DELTA Fiber Nederland BV", + "NORDUNet", + "Aire Networks del Mediterraneo S.L.U.", + "CESNET z.s.p.o.", + "Internet Systems Consortium, F-Root Operator", + "Telenor", + "T-mobile thuis", + "Emirates Telecommunications Corporation", + "Gelderland Internet Exchange", + "Jaguar Network", + "IP-Only", + "A2B Internet", + "Netnod", + "TransIP", + "Iceland Telecom Ltd.", + "Viatel Ltd", + "IT-Ernity Internet Services B.V.", + "MTS", + "Redraw Internet", + "Amazon", + "GTT", + "BT", + "Core Backbone GmbH", + "Booking.com", + "PCCW Global", + "Job Snijders", + "BGPMUX / PEERING TestBed", + "AS112 Project (Hosted by RIPE NCC at AMS-IX)", + "Claranet Ltd.", + "Microsoft", + "Nextlayer.at", + "Serverius", + "Wikimedia Foundation", + "Hostserver.de", + "Automattic Inc.", + "AFNIC", + "OVH", + "I3D", + "Vodafone Group", + "SOCO Network Solutions GmbH", + "Zayo France", + "Cloudflare", + "Facebook", + "Liberty Global", + "Zayo / Abovenet", + "Deutsche Telekom", + "TeliaSonera International Carrier", + "ColocationIX", + "NFOrce Entertainment B.V.", + "Knipp Medien und Kommunikation GmbH", + "Qonnected B.V.", + "Computerline Group", + "GlobalConnect Group", + "Micron21", + "Sprint", + "Twitch / Justin.tv", + "Global Network Management", + "SURF", + "NovoServe", + "Yahoo", + "PCextreme", + "Belgacom International Carrier Services (aka BICS)", + "Google", + "Zain Group", + "SIG-Telecom", + "Bandwidth Technologies", + "Vivor", + "Tata", + "Tele2 SWIPNet", + "Weservit B.V.", + "Fastly inc.", + "PeakFactory", + "Telecom Italia Sparkle", + "packet.net", + "Telxius", + "Asteroid Route Collector", + "Asteroid MLP Route Server", + "Twitter", + "SPEED-IX route server peers", + "Freifunk Nordwest e.V", + "e-utp.net", + "Belcloud", + "BlackGATE BV", + "T-2 d.o.o.", + "Canadian Internet Registration Authority", + "Dropbox Inc", + "Accenture BV", + "myLoc managed IT AG", + "Redhosting B.V.", + "Chronos", + "Servperso-Systems", + "Stichting Nationale Beheersorganisatie Internet Providers (NBIP)", + "Hetzner Online GmbH", + "Valve Corporation", + "Parknet F.M.B.A.", + "CLOUDITY Network", + "AltusHost B.V.", + "Mythic Beasts Ltd", + "CS Net", + "Cynthia Maja Revstrom", + "Stichting EventInfra", + "TRIX", + "EdgeCast Networks Inc", + "Prolocation B.V.", + "Speakup", + "Seacom", + "SAM Office B.V.", + "OSN Online Service Nuernberg GmbH", + "Tencent Holdings Ltd.", + "Green Mini host BV", + "WD6.net B.V.", + "Meanie", + "ADES / NetLogics", + "Nederlandse Publieke Omroep - NPO", + "Alpine North", + "Greenhost", + "Freedom Internet", + "Unithost Internet BV", + "Nedap NV", + "Serverion B.V.", + "Frys-IX Route Servers", + "IPng Networks GmbH", + "DE-CIX Frankfurt Route Servers", + "France-IX Paris Route Servers", + "SwissIX Route Servers", + "communityrack.org", + "Cato Networks Ltd", + "HostIn", + "equada network GmbH", + "NetActuate Inc.", + "Solutions4xs", + "Kviknet.dk ApS", + "NIKHEF", + "China Telecom", + "IONOS SE", + "Earthlink Telecommunications Equipment Trading & Services DMCC", + "OpenFiber", + "ESEVEN DevOps", + "LOCIX NL Route Servers", + "Sentia Labs", + "Gandi", + "Xyphen IT", + "NetOne NL", + "LUJE.net", + "INTERIX Route Servers", + "Ursin Filli", + "Apple Communications Ltd.", + "Sipartech SAS", + "Nick Bouwhuis", + "CBWS", + "Peering with Coloclue", + "Coloclue is present at the following exchanges:", + "AMS-IX: 80.249.211.161 / 2001:7f8:1::a500:8283:1", + "Asteroid Amsterdam: 185.1.94.15 / 2001:7f8:b6::205b:1", + "DE-CIX Frankfurt: 80.80.197.51 / 2001:7b8::205b:0:1", + "France-IX Paris: 37.49.238.29 / 2001:7b8:54::2:29", + "Frys-IX: 185.1.203.140 / 2001:7b8:10f::205b:140", + "Frys-IX: 185.1.203.187 / 2001:7f8:10f::205b:187", + "NL-IX: 193.239.117.111 / 2001:7f8:13::a500:8283:1", + "NL-IX: 193.239.117.203 / 2001:7f8:13::a500:8283:2", + "Speed-IX: 185.1.222.16 / 2001:7f8:b7::a500:8283:1", + "SwissIX: 91.206.53.24 / 2001:7b8:24::118", + "There are two methods:", + "Modern way:", + "- Send a pull request on github:", + "http://github.com/coloclue/peering", + "- Modify peers.yaml", + "- Wait for Travis' OK signal", + "- Peering establishes automatically!", + "Old-fashioned way:", + "- Send an email to routers@coloclue.net", + "- Please do include your as-set or route-set", + "- Wait for AS8283 engineer to update peers.yaml", + "Abuse issues should be reported to abuse@coloclue.net", + "Abuse related e-mail to any other address(es)", + "will *not* be read", + "Routing Policy", + "- All peering sessions are strictly filtered based on IRR data", + "- RPKI INVALID prefixes are rejected", + "BGP Communities and Traffic Engineering", + "=======================================", + "The following BGP Community scheme draws inspiration from:", + "draft-ietf-grow-large-communities-usage", + "https://tools.ietf.org/html/draft-ietf-grow-large-communities-usage", + "INFORMATIONAL COMMUNITIES:", + "==========================", + "RFC 1997 | Large | Meaning (Informational)", + "----------|-------------|-------------------------------------", + "8283:1 | 8283:0:1 | peering routes", + "8283:2 | 8283:0:2 | downstream routes", + "----------+-------------+-------------------------------------", + "8283:101 | 8283:5:1 | Accepted from peer because of valid IRR entry", + "8283:102 | 8283:5:2 | Accepted from peer because of valid ROA", + "8283:104 | 8283:5:4 | Accepted while RPKI invalid because it is added to our whitelist", + "8283:10 | 8283:6:10 | received from TRUE", + "8283:11 | 8283:6:11 | received from Atom86", + "8283:14 | 8283:6:14 | received from FiberRing", + "8283:15 | 8283:6:15 | received from Fusix", + "- | 8283:8:26 | received via AMS-IX", + "- | 8283:8:64 | received via NL-IX", + "- | 8283:8:1812 | received via Asteroid", + "- | 8283:8:1842 | received via SpeedIX", + "- | 8283:8:3512 | received via Frys-IX", + "- | 8283:8:31 | received via DE-CIX", + "- | 8283:8:359 | received via France-IX", + "- | 8283:8:60 | received via SwissIX", + "- | 8283:8:2601 | received via LocIX Netherlands", + "| Rejection Reasons", + "- | 8283:7:1 | More specifics covering AS8283 space are considered hijacks", + "- | 8283:7:2 | Somewhere in the AS_PATH a Bogon ASN is present (0, 23456, 64496..65534, 4200000000+)", + "- | 8283:7:3 | The prefix is Bogon garbage (rfc1918, rfc4291 etc)", + "- | 8283:7:4 | The prefix is an RPKI Invalid and as such rejected", + "- | 8283:7:5 | The route's prefix length is unacceptable (too small or too large)", + "- | 8283:7:6 | There is no IRR object that covers this route announcement", + "- | 8283:7:7 | Coloclue member is not authorized to announce this route", + "ACTION COMMUNITIES:", + "===================", + "RFC 1997 | Large | Meaning (Action)", + "8283:50 | 8283:1:50 | Set LOCAL_PREF to 50 (default is 100)", + "8283:150 | 8283:1:150 | Set LOCAL_PREF to 150 (default is 100)", + "8283:202 | 8283:2:0 | Prepend 8283 once to all eBGP peers", + "- | 8283:2:nnn | Prepend 8283 once to peer AS nnn", + "8283:203 | 8283:3:0 | Prepend 8283 twice to all eBGP peers", + "- | 8283:3:nnn | Prepend 8283 twice to peer AS nnn", + "8283:204 | 8283:4:0 | Do not export to eBGP peers", + "- | 8283:4:nnn | Do not export to peer AS nnn", + "65535:0 | - | G-SHUT [draft-ietf-grow-bgp-gshut]", + "65535:666 | - | BLACKHOLE [RFC 7999]", + "MEMBERS ONLY:", + "=============", + "----------+------------+-------------------------------------", + "8283:50 | 8283:1:50 | Set LOCAL_PREF to 50 (default is 500)", + "| *** WARNING: default is 100 for other routes, so your prefixes may be become unreachable!", + "8283:450 | 8283:1:450 | Set LOCAL_PREF to 450 (default is 500)", + "8283:550 | 8283:1:550 | Set LOCAL_PREF to 550 (default is 500)", + "8283:301 | 8283:301:0 | Do not export to other Coloclue members", + "8283:302 | 8283:302:0 | Do not export to transit providers", + "8283:303 | 8283:303:0 | Do not export to direct peers", + "Examples:", + "---------", + "8283:2:3320 - prepend 8283 to AS 3320 (path on export will be 8283_8283_you)", + "8283:3:200023 - prepend 8283 to AS 200023 (path on export will be 8283_8283_8283_you)", + "8283:4:198203 - do not announce the route over any BGP sessions with AS 198203", + "(notice how you can 32-bit ASNs? :-)" + ] + } + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "self", + "href": "https://rdap.db.ripe.net/autnum/8283" + }, + { + "value": "http://www.ripe.net/data-tools/support/documentation/terms", + "rel": "copyright", + "href": "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2003-06-23T14:40:34Z" + }, + { + "eventAction": "last changed", + "eventDate": "2023-11-27T23:02:44Z" + } + ], + "rdapConformance": [ + "nro_rdap_profile_asn_flat_0", + "cidr0", + "rdap_level_0", + "nro_rdap_profile_0" + ], + "notices": [ + { + "title": "Filtered", + "description": [ + "This output has been filtered." + ] + }, + { + "title": "Whois Inaccuracy Reporting", + "description": [ + "If you see inaccuracies in the results, please visit:" + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "inaccuracy-report", + "href": "https://www.ripe.net/contact-form?topic=ripe_dbm&show_form=true", + "type": "text/html" + } + ] + }, + { + "title": "Source", + "description": [ + "Objects returned came from source", + "RIPE" + ] + }, + { + "title": "Terms and Conditions", + "description": [ + "This is the RIPE Database query service. The objects are in RDAP format." + ], + "links": [ + { + "value": "https://rdap.db.ripe.net/autnum/8283", + "rel": "terms-of-service", + "href": "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "type": "application/pdf" + } + ] + } + ], + "port43": "whois.ripe.net", + "objectClassName": "autnum" +} \ No newline at end of file diff --git a/tests/data/normalize/domain/20c.com.expected b/tests/data/normalize/domain/20c.com.expected new file mode 100644 index 0000000..43ccd48 --- /dev/null +++ b/tests/data/normalize/domain/20c.com.expected @@ -0,0 +1,74 @@ +{ + "created": "2004-06-28T18:28:14Z", + "updated": "2024-06-25T03:31:37Z", + "name": "20C.COM", + "handle": "123664426_DOMAIN_COM-VRSN", + "dns_sec": "insecure", + "nameservers": [ + { + "host": "NS-1468.AWSDNS-55.ORG" + }, + { + "host": "NS-1771.AWSDNS-29.CO.UK" + }, + { + "host": "NS-327.AWSDNS-40.COM" + }, + { + "host": "NS-545.AWSDNS-04.NET" + } + ], + "contacts": [], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + }, + { + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "handle": "AS8283", + "urls": [ + "https://rdap.org/autnum/8283", + "https://rdap.org/autnum/8283" + ], + "description": null + }, + { + "created": null, + "updated": "2017-09-21T23:55:48Z", + "handle": "CLUE1-RIPE", + "urls": [ + "https://rdap.org/entity/CLUE1-RIPE", + "https://rdap.org/entity/CLUE1-RIPE" + ], + "description": null + }, + { + "created": null, + "updated": "2017-06-13T22:21:32Z", + "handle": "DJVG", + "urls": [ + "https://rdap.org/entity/DJVG", + "https://rdap.org/entity/DJVG" + ], + "description": null + }, + { + "created": "2004-06-28T18:28:14Z", + "updated": "2024-06-25T03:31:37Z", + "handle": "123664426_DOMAIN_COM-VRSN", + "urls": [ + "https://rdap.org/domain/20c.com", + "https://rdap.org/domain/20c.com" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/domain/20c.com.input b/tests/data/normalize/domain/20c.com.input new file mode 100644 index 0000000..03e8fe7 --- /dev/null +++ b/tests/data/normalize/domain/20c.com.input @@ -0,0 +1,31 @@ +{ + "name": "20C.COM", + "handle": "123664426_DOMAIN_COM-VRSN", + "dns_sec": "insecure", + "contacts": [], + "nameservers": [ + { + "host": "NS-1468.AWSDNS-55.ORG" + }, + { + "host": "NS-1771.AWSDNS-29.CO.UK" + }, + { + "host": "NS-327.AWSDNS-40.COM" + }, + { + "host": "NS-545.AWSDNS-04.NET" + } + ], + "sources": [ + { + "handle": "123664426_DOMAIN_COM-VRSN", + "urls": [ + "https://rdap.org/domain/20c.com", + "https://rdap.verisign.com/com/v1/domain/20c.com" + ], + "created": "2004-06-28T18:28:14Z", + "updated": "2024-06-25T03:31:37Z" + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/entity/CLUE1-RIPE.expected b/tests/data/normalize/entity/CLUE1-RIPE.expected new file mode 100644 index 0000000..b0dcaa2 --- /dev/null +++ b/tests/data/normalize/entity/CLUE1-RIPE.expected @@ -0,0 +1,63 @@ +{ + "created": null, + "updated": "2017-09-21T23:55:48Z", + "name": "CLUE1-RIPE", + "organization": null, + "locations": [ + { + "updated": "2017-09-21T23:55:48Z", + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Frans Duwaerstraat 34\n1318AC Almere\nNetherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + } + ], + "contacts": [ + { + "created": null, + "updated": "2017-09-21T23:55:48Z", + "name": "Netwerkvereniging Coloclue", + "roles": [], + "phone": "+31 6 51387718", + "email": "routers@coloclue.net" + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + }, + { + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "handle": "AS8283", + "urls": [ + "https://rdap.org/autnum/8283", + "https://rdap.org/autnum/8283" + ], + "description": null + }, + { + "created": null, + "updated": "2017-09-21T23:55:48Z", + "handle": "CLUE1-RIPE", + "urls": [ + "https://rdap.org/entity/CLUE1-RIPE", + "https://rdap.org/entity/CLUE1-RIPE" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/entity/CLUE1-RIPE.input b/tests/data/normalize/entity/CLUE1-RIPE.input new file mode 100644 index 0000000..b3bd076 --- /dev/null +++ b/tests/data/normalize/entity/CLUE1-RIPE.input @@ -0,0 +1,85 @@ +{ + "handle" : "CLUE1-RIPE", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "Netwerkvereniging Coloclue" ], [ "kind", { }, "text", "group" ], [ "adr", { + "label" : "Frans Duwaerstraat 34\n1318AC Almere\nNetherlands" + }, "text", null ], [ "tel", { + "type" : "voice" + }, "text", "+31651387718" ], [ "email", { }, "text", "ops@coloclue.net" ], [ "email", { }, "text", "routers@coloclue.net" ] ] ], + "entities" : [ { + "handle" : "COLOCLUE-MNT", + "roles" : [ "registrant" ], + "objectClassName" : "entity" + }, { + "handle" : "JB17421-RIPE", + "roles" : [ "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "JL9785-RIPE", + "roles" : [ "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "JVI-RIPE", + "roles" : [ "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "MS44437-RIPE", + "roles" : [ "administrative", "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "MWTS1-RIPE", + "roles" : [ "administrative" ], + "objectClassName" : "entity" + }, { + "handle" : "NMR5-RIPE", + "roles" : [ "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "NT1031-RIPE", + "roles" : [ "administrative" ], + "objectClassName" : "entity" + }, { + "handle" : "PDW-RIPE", + "roles" : [ "administrative", "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "PEER-RIPE", + "roles" : [ "technical" ], + "objectClassName" : "entity" + }, { + "handle" : "TIJN-RIPE", + "roles" : [ "administrative", "technical" ], + "objectClassName" : "entity" + } ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/CLUE1-RIPE", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/CLUE1-RIPE" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-09-21T23:55:48Z" + } ], + "rdapConformance" : [ "rdap_level_0" ], + "notices" : [ { + "title" : "Filtered", + "description" : [ "This output has been filtered." ] + }, { + "title" : "Source", + "description" : [ "Objects returned came from source", "RIPE" ] + }, { + "title" : "Terms and Conditions", + "description" : [ "This is the RIPE Database query service. The objects are in RDAP format." ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/CLUE1-RIPE", + "rel" : "terms-of-service", + "href" : "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "type" : "application/pdf" + } ] + } ], + "port43" : "whois.ripe.net", + "objectClassName" : "entity" +} \ No newline at end of file diff --git a/tests/data/normalize/entity/DJVG.expected b/tests/data/normalize/entity/DJVG.expected new file mode 100644 index 0000000..a71d961 --- /dev/null +++ b/tests/data/normalize/entity/DJVG.expected @@ -0,0 +1,73 @@ +{ + "created": null, + "updated": "2017-06-13T22:21:32Z", + "name": "DJVG", + "organization": null, + "locations": [ + { + "updated": "2017-06-13T22:21:32Z", + "country": "US", + "city": "Mountain View", + "postal_code": "94043", + "address": "Postbus 8160\n1180LD Amstelveen\nthe Netherlands", + "geo": { + "latitude": 37.4224764, + "longitude": -122.0842499 + }, + "floor": "4", + "suite": "Suite 200" + } + ], + "contacts": [ + { + "created": null, + "updated": "2017-06-13T22:21:32Z", + "name": "Daan van Gorkum", + "roles": [], + "phone": "+31 20 308 0063", + "email": "daan.vangorkum@vusam.com" + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + }, + { + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "handle": "AS8283", + "urls": [ + "https://rdap.org/autnum/8283", + "https://rdap.org/autnum/8283" + ], + "description": null + }, + { + "created": null, + "updated": "2017-09-21T23:55:48Z", + "handle": "CLUE1-RIPE", + "urls": [ + "https://rdap.org/entity/CLUE1-RIPE", + "https://rdap.org/entity/CLUE1-RIPE" + ], + "description": null + }, + { + "created": null, + "updated": "2017-06-13T22:21:32Z", + "handle": "DJVG", + "urls": [ + "https://rdap.org/entity/DJVG", + "https://rdap.org/entity/DJVG" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/entity/DJVG.input b/tests/data/normalize/entity/DJVG.input new file mode 100644 index 0000000..01d656a --- /dev/null +++ b/tests/data/normalize/entity/DJVG.input @@ -0,0 +1,45 @@ +{ + "handle" : "DJVG", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "Daan van Gorkum" ], [ "kind", { }, "text", "individual" ], [ "adr", { + "label" : "Postbus 8160\n1180LD Amstelveen\nthe Netherlands" + }, "text", null ], [ "tel", { + "type" : "voice" + }, "text", "+31203080063" ], [ "email", { }, "text", "daan.vangorkum@vusam.com" ] ] ], + "entities" : [ { + "handle" : "VUSAM", + "roles" : [ "registrant" ], + "objectClassName" : "entity" + } ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/DJVG", + "rel" : "self", + "href" : "https://rdap.db.ripe.net/entity/DJVG" + }, { + "value" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "href" : "http://www.ripe.net/data-tools/support/documentation/terms" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-06-13T22:21:32Z" + } ], + "rdapConformance" : [ "rdap_level_0" ], + "notices" : [ { + "title" : "Filtered", + "description" : [ "This output has been filtered." ] + }, { + "title" : "Source", + "description" : [ "Objects returned came from source", "RIPE" ] + }, { + "title" : "Terms and Conditions", + "description" : [ "This is the RIPE Database query service. The objects are in RDAP format." ], + "links" : [ { + "value" : "https://rdap.db.ripe.net/entity/DJVG", + "rel" : "terms-of-service", + "href" : "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "type" : "application/pdf" + } ] + } ], + "port43" : "whois.ripe.net", + "objectClassName" : "entity" +} \ No newline at end of file diff --git a/tests/data/normalize/ip/206.41.110.0.expected b/tests/data/normalize/ip/206.41.110.0.expected new file mode 100644 index 0000000..67654d8 --- /dev/null +++ b/tests/data/normalize/ip/206.41.110.0.expected @@ -0,0 +1,116 @@ +{ + "created": "2014-10-17T18:32:20-04:00", + "updated": "2021-12-14T20:28:45-05:00", + "prefix": "206.41.110.0/24", + "version": 4, + "name": "CHIX", + "type": "DIRECT ALLOCATION", + "status": "active", + "parent": "206.0.0.0/8", + "contacts": [ + { + "created": "2022-07-06T18:47:31-04:00", + "updated": "2024-06-04T14:09:08-04:00", + "name": "Jordan Hazen", + "roles": [ + "technical" + ], + "phone": "+1 904-549-7540", + "email": "jhazen@sbaedge.com" + }, + { + "created": "2021-01-22T13:10:25-05:00", + "updated": "2024-01-23T08:35:36-05:00", + "name": "Network Operations", + "roles": [ + "admin", + "technical" + ], + "phone": null, + "email": "netops@sbaedge.com" + }, + { + "created": "2014-11-19T00:39:22-05:00", + "updated": "2023-09-05T13:27:59-04:00", + "name": "United IX Engineers", + "roles": [ + "abuse", + "technical" + ], + "phone": "+1 877-432-2656", + "email": "neteng@unitedix.net" + }, + { + "created": "2024-05-30T10:27:44-04:00", + "updated": "2024-05-30T10:42:04-04:00", + "name": "Vitalii Kukanov", + "roles": [ + "technical" + ], + "phone": "+1 877-432-2656", + "email": "vkukanov@sbaedge.com" + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.org/autnum/63311" + ], + "description": null + }, + { + "created": "2003-06-23T14:40:34Z", + "updated": "2023-11-27T23:02:44Z", + "handle": "AS8283", + "urls": [ + "https://rdap.org/autnum/8283", + "https://rdap.org/autnum/8283" + ], + "description": null + }, + { + "created": null, + "updated": "2017-09-21T23:55:48Z", + "handle": "CLUE1-RIPE", + "urls": [ + "https://rdap.org/entity/CLUE1-RIPE", + "https://rdap.org/entity/CLUE1-RIPE" + ], + "description": null + }, + { + "created": null, + "updated": "2017-06-13T22:21:32Z", + "handle": "DJVG", + "urls": [ + "https://rdap.org/entity/DJVG", + "https://rdap.org/entity/DJVG" + ], + "description": null + }, + { + "created": "2004-06-28T18:28:14Z", + "updated": "2024-06-25T03:31:37Z", + "handle": "123664426_DOMAIN_COM-VRSN", + "urls": [ + "https://rdap.org/domain/20c.com", + "https://rdap.org/domain/20c.com" + ], + "description": null + }, + { + "created": "2014-10-17T18:32:20-04:00", + "updated": "2021-12-14T20:28:45-05:00", + "handle": "NET-206-41-110-0-1", + "urls": [ + "https://rdap.org/ip/206.41.110.0", + "https://rdap.org/ip/206.41.110.0" + ], + "description": null + } + ] +} \ No newline at end of file diff --git a/tests/data/normalize/ip/206.41.110.0.input b/tests/data/normalize/ip/206.41.110.0.input new file mode 100644 index 0000000..11008e0 --- /dev/null +++ b/tests/data/normalize/ip/206.41.110.0.input @@ -0,0 +1,584 @@ +{ + "rdapConformance": [ + "nro_rdap_profile_0", + "rdap_level_0", + "cidr0", + "arin_originas0" + ], + "notices": [ + { + "title": "Terms of Service", + "description": [ + "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "terms-of-service", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/tou/" + } + ] + }, + { + "title": "Whois Inaccuracy Reporting", + "description": [ + "If you see inaccuracies in the results, please visit: " + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "inaccuracy-report", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } + ] + }, + { + "title": "Copyright Notice", + "description": [ + "Copyright 1997-2024, American Registry for Internet Numbers, Ltd." + ], + "links": [] + } + ], + "handle": "NET-206-41-110-0-1", + "startAddress": "206.41.110.0", + "endAddress": "206.41.110.255", + "ipVersion": "v4", + "name": "CHIX", + "type": "DIRECT ALLOCATION", + "parentHandle": "NET-206-0-0-0-0", + "remarks": [ + { + "title": "Registration Comments", + "description": [ + "https://unitedix.net/" + ] + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2021-12-14T20:28:45-05:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-10-17T18:32:20-04:00" + } + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/ip/206.41.110.0" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/net/NET-206-41-110-0-1" + } + ], + "entities": [ + { + "handle": "UIEL", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "United-IX" + ], + [ + "adr", + { + "label": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "org" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/UIEL" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/org/UIEL" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-06-04T14:12:37-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-07-11T10:33:06-04:00" + } + ], + "status": [], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [ + { + "handle": "UIE-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "United IX Engineers" + ], + [ + "org", + {}, + "text", + "United IX Engineers" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "neteng@unitedix.net" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656" + ] + ] + ], + "roles": [ + "abuse", + "noc", + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/UIE-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/UIE-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2023-09-05T13:27:59-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-11-19T00:39:22-05:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "HAZEN16-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "421 W Church St\nJacksonville\nFL\n32202\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Jordan Hazen" + ], + [ + "n", + {}, + "text", + [ + "Hazen", + "Jordan", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "jhazen@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-904-549-7540" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/HAZEN16-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/HAZEN16-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-06-04T14:09:08-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2022-07-06T18:47:31-04:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "KUKAN5-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "603 Discovery Dr.\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Vitalii Kukanov" + ], + [ + "n", + {}, + "text", + [ + "Kukanov", + "Vitalii", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "vkukanov@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/KUKAN5-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/KUKAN5-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-05-30T10:42:04-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2024-05-30T10:27:44-04:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "NETWO9391-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "8051 Congress Ave\nBoca Raton\nFL\n33487\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Network Operations" + ], + [ + "org", + {}, + "text", + "Network Operations" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "netops@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656;ext201" + ] + ] + ], + "roles": [ + "administrative", + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/NETWO9391-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/NETWO9391-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-01-23T08:35:36-05:00" + }, + { + "eventAction": "registration", + "eventDate": "2021-01-22T13:10:25-05:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + } + ] + } + ], + "port43": "whois.arin.net", + "status": [ + "active" + ], + "objectClassName": "ip network", + "cidr0_cidrs": [ + { + "v4prefix": "206.41.110.0", + "length": 24 + } + ], + "arin_originas0_originautnums": [] +} \ No newline at end of file diff --git a/tests/data/rdap/domain/20c.com.input b/tests/data/rdap/domain/20c.com.input new file mode 100644 index 0000000..be07625 --- /dev/null +++ b/tests/data/rdap/domain/20c.com.input @@ -0,0 +1,208 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "icann_rdap_technical_implementation_guide_0", + "icann_rdap_response_profile_0" + ], + "notices": [ + { + "title": "Terms of Use", + "description": [ + "Service subject to Terms of Use." + ], + "links": [ + { + "value": null, + "rel": null, + "type": "text/html", + "href": "https://www.verisign.com/domain-names/registration-data-access-protocol/terms-service/index.xhtml" + } + ] + }, + { + "title": "Status Codes", + "description": [ + "For more information on domain status codes, please visit https://icann.org/epp" + ], + "links": [ + { + "value": null, + "rel": null, + "type": "text/html", + "href": "https://icann.org/epp" + } + ] + }, + { + "title": "RDDS Inaccuracy Complaint Form", + "description": [ + "URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf" + ], + "links": [ + { + "value": null, + "rel": null, + "type": "text/html", + "href": "https://icann.org/wicf" + } + ] + } + ], + "handle": "123664426_DOMAIN_COM-VRSN", + "ldhName": "20C.COM", + "events": [ + { + "eventAction": "registration", + "eventDate": "2004-06-28T18:28:14Z" + }, + { + "eventAction": "expiration", + "eventDate": "2025-06-28T18:28:14Z" + }, + { + "eventAction": "last changed", + "eventDate": "2024-06-25T03:31:37Z" + }, + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-07-24T18:48:30Z" + } + ], + "links": [ + { + "value": "https://rdap.verisign.com/com/v1/domain/20C.COM", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.verisign.com/com/v1/domain/20C.COM" + }, + { + "value": "https://rdap.joker.com/domain/20C.COM", + "rel": "related", + "type": "application/rdap+json", + "href": "https://rdap.joker.com/domain/20C.COM" + } + ], + "entities": [ + { + "handle": "113", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "CSL Computer Service Langenbach GmbH d/b/a joker.com" + ] + ] + ], + "roles": [ + "registrar" + ], + "links": [], + "events": [], + "status": [], + "port43": "", + "objectClassName": "entity", + "remarks": [], + "entities": [ + { + "handle": "", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+49.21186767447" + ], + [ + "email", + {}, + "text", + "abuse@joker.com" + ] + ] + ], + "roles": [ + "abuse" + ], + "links": [], + "events": [], + "status": [], + "port43": "", + "objectClassName": "entity", + "remarks": [], + "entities": [] + } + ] + } + ], + "port43": "", + "network": null, + "objectClassName": "domain", + "secureDNS": { + "delegationSigned": false, + "zeroSigned": null, + "dsData": [] + }, + "nameservers": [ + { + "objectClassName": "nameserver", + "ldhName": "NS-1468.AWSDNS-55.ORG", + "unicodeName": null, + "ipAddresses": {}, + "remarks": [], + "port43": null, + "events": [] + }, + { + "objectClassName": "nameserver", + "ldhName": "NS-1771.AWSDNS-29.CO.UK", + "unicodeName": null, + "ipAddresses": {}, + "remarks": [], + "port43": null, + "events": [] + }, + { + "objectClassName": "nameserver", + "ldhName": "NS-327.AWSDNS-40.COM", + "unicodeName": null, + "ipAddresses": {}, + "remarks": [], + "port43": null, + "events": [] + }, + { + "objectClassName": "nameserver", + "ldhName": "NS-545.AWSDNS-04.NET", + "unicodeName": null, + "ipAddresses": {}, + "remarks": [], + "port43": null, + "events": [] + } + ] +} \ No newline at end of file diff --git a/tests/data/rdap/ip/206.41.110.0.input b/tests/data/rdap/ip/206.41.110.0.input new file mode 100644 index 0000000..11008e0 --- /dev/null +++ b/tests/data/rdap/ip/206.41.110.0.input @@ -0,0 +1,584 @@ +{ + "rdapConformance": [ + "nro_rdap_profile_0", + "rdap_level_0", + "cidr0", + "arin_originas0" + ], + "notices": [ + { + "title": "Terms of Service", + "description": [ + "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "terms-of-service", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/tou/" + } + ] + }, + { + "title": "Whois Inaccuracy Reporting", + "description": [ + "If you see inaccuracies in the results, please visit: " + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "inaccuracy-report", + "type": "text/html", + "href": "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } + ] + }, + { + "title": "Copyright Notice", + "description": [ + "Copyright 1997-2024, American Registry for Internet Numbers, Ltd." + ], + "links": [] + } + ], + "handle": "NET-206-41-110-0-1", + "startAddress": "206.41.110.0", + "endAddress": "206.41.110.255", + "ipVersion": "v4", + "name": "CHIX", + "type": "DIRECT ALLOCATION", + "parentHandle": "NET-206-0-0-0-0", + "remarks": [ + { + "title": "Registration Comments", + "description": [ + "https://unitedix.net/" + ] + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2021-12-14T20:28:45-05:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-10-17T18:32:20-04:00" + } + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/ip/206.41.110.0" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/net/NET-206-41-110-0-1" + } + ], + "entities": [ + { + "handle": "UIEL", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "United-IX" + ], + [ + "adr", + { + "label": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "org" + ] + ] + ], + "roles": [ + "registrant" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/UIEL" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/org/UIEL" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-06-04T14:12:37-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-07-11T10:33:06-04:00" + } + ], + "status": [], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [ + { + "handle": "UIE-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "United IX Engineers" + ], + [ + "org", + {}, + "text", + "United IX Engineers" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "neteng@unitedix.net" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656" + ] + ] + ], + "roles": [ + "abuse", + "noc", + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/UIE-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/UIE-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2023-09-05T13:27:59-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2014-11-19T00:39:22-05:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "HAZEN16-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "421 W Church St\nJacksonville\nFL\n32202\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Jordan Hazen" + ], + [ + "n", + {}, + "text", + [ + "Hazen", + "Jordan", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "jhazen@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-904-549-7540" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/HAZEN16-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/HAZEN16-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-06-04T14:09:08-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2022-07-06T18:47:31-04:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "KUKAN5-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "603 Discovery Dr.\nWest Chicago\nIL\n60185\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Vitalii Kukanov" + ], + [ + "n", + {}, + "text", + [ + "Kukanov", + "Vitalii", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "vkukanov@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656" + ] + ] + ], + "roles": [ + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/KUKAN5-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/KUKAN5-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-05-30T10:42:04-04:00" + }, + { + "eventAction": "registration", + "eventDate": "2024-05-30T10:27:44-04:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + }, + { + "handle": "NETWO9391-ARIN", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label": "8051 Congress Ave\nBoca Raton\nFL\n33487\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Network Operations" + ], + [ + "org", + {}, + "text", + "Network Operations" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "netops@sbaedge.com" + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ] + }, + "text", + "+1-877-432-2656;ext201" + ] + ] + ], + "roles": [ + "administrative", + "technical" + ], + "links": [ + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "self", + "type": "application/rdap+json", + "href": "https://rdap.arin.net/registry/entity/NETWO9391-ARIN" + }, + { + "value": "https://rdap.arin.net/registry/ip/206.41.110.0", + "rel": "alternate", + "type": "application/xml", + "href": "https://whois.arin.net/rest/poc/NETWO9391-ARIN" + } + ], + "events": [ + { + "eventAction": "last changed", + "eventDate": "2024-01-23T08:35:36-05:00" + }, + { + "eventAction": "registration", + "eventDate": "2021-01-22T13:10:25-05:00" + } + ], + "status": [ + "validated" + ], + "port43": "whois.arin.net", + "objectClassName": "entity", + "remarks": [], + "entities": [] + } + ] + } + ], + "port43": "whois.arin.net", + "status": [ + "active" + ], + "objectClassName": "ip network", + "cidr0_cidrs": [ + { + "v4prefix": "206.41.110.0", + "length": 24 + } + ], + "arin_originas0_originautnums": [] +} \ No newline at end of file diff --git a/tests/test_geo.py b/tests/test_geo.py new file mode 100644 index 0000000..565be8d --- /dev/null +++ b/tests/test_geo.py @@ -0,0 +1,102 @@ +import pytest +from unittest.mock import patch +from datetime import datetime +from rdap.schema.normalized import Location, GeoLocation +from rdap.context import RdapRequestState + +# Import the functions and exceptions from your module +from rdap.normalize.geo import ( + normalize, NotFound, GoogleKeyNotSet +) + +# Mock data +MOCK_GEOCODE_RESULT = [{ + "formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", + "geometry": { + "location": { + "lat": 37.4224764, + "lng": -122.0842499 + } + }, + "address_components": [ + {"types": ["country"], "short_name": "US"}, + {"types": ["postal_code"], "long_name": "94043"}, + {"types": ["locality"], "long_name": "Mountain View"}, + {"types": ["floor"], "long_name": "4"}, + {"types": ["subpremise"], "long_name": "Suite 200"}, + ] +}] + +@pytest.fixture +def mock_google_client(): + with patch('googlemaps.Client') as mock_client: + yield mock_client + +@pytest.fixture +def mock_rdap_request(): + with patch('rdap.context.rdap_request') as mock_request: + mock_request.get.return_value = RdapRequestState() + yield mock_request + +@pytest.fixture(autouse=True) +def mock_google_api_key(): + with patch.dict('os.environ', {'GOOGLE_MAPS_API_KEY': 'fake_api_key'}): + yield + +def test_normalize_success(mock_google_client): + mock_client = mock_google_client.return_value + mock_client.geocode.return_value = MOCK_GEOCODE_RESULT + + date = datetime.now() + + # Mock the lookup function directly + with patch('rdap.normalize.geo.lookup') as mock_lookup: + # Return the first item of the list + mock_lookup.return_value = MOCK_GEOCODE_RESULT[0] + result = normalize("1600 Amphitheatre Parkway, Mountain View, CA", date) + + + assert isinstance(result, Location) + assert result.updated == date + assert result.country == "US" + assert result.city == "Mountain View" + assert result.postal_code == "94043" + assert result.floor == "4" + assert result.suite == "Suite 200" + assert result.address == "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA" + assert isinstance(result.geo, GeoLocation) + assert result.geo.latitude == 37.4224764 + assert result.geo.longitude == -122.0842499 + + # Verify that lookup was called + mock_lookup.assert_called_once_with("1600 Amphitheatre Parkway, Mountain View, CA", None) + +def test_normalize_google_key_not_set(): + with patch('rdap.normalize.geo.lookup', side_effect=GoogleKeyNotSet): + date = datetime.now() + result = normalize("1600 Amphitheatre Parkway, Mountain View, CA", date) + + assert isinstance(result, Location) + assert result.updated == date + assert result.address == "1600 Amphitheatre Parkway, Mountain View, CA" + assert result.country is None + assert result.city is None + assert result.postal_code is None + assert result.floor is None + assert result.suite is None + assert result.geo is None + +def test_normalize_not_found(): + with patch('rdap.normalize.geo.lookup', side_effect=NotFound): + date = datetime.now() + result = normalize("Non-existent Address", date) + + assert isinstance(result, Location) + assert result.updated == date + assert result.address == "Non-existent Address" + assert result.country is None + assert result.city is None + assert result.postal_code is None + assert result.floor is None + assert result.suite is None + assert result.geo is None \ No newline at end of file diff --git a/tests/test_normalize.py b/tests/test_normalize.py new file mode 100644 index 0000000..fe6e637 --- /dev/null +++ b/tests/test_normalize.py @@ -0,0 +1,65 @@ +import pytest +import os +import json +import pytest_filedata +import copy +from unittest.mock import patch + +from tests.test_geo import MOCK_GEOCODE_RESULT + +def dynamic_mock_address(formatted_address, client): + result = copy.deepcopy(MOCK_GEOCODE_RESULT[0]) + result["formatted_address"] = f"{formatted_address}" + return result + + +@pytest.fixture(autouse=True) +def set_google_maps_api_key(): + original_key = os.environ.get('GOOGLE_MAPS_API_KEY') + os.environ['GOOGLE_MAPS_API_KEY'] = 'your_test_api_key' + yield + if original_key is not None: + os.environ['GOOGLE_MAPS_API_KEY'] = original_key + else: + del os.environ['GOOGLE_MAPS_API_KEY'] + +@pytest_filedata.RequestsData("rdap") +def test_rdap_asn_lookup(rdapc, data_normalize_autnum): + with patch("rdap.normalize.geo.lookup") as mock_lookup: + mock_lookup.side_effect = dynamic_mock_address + asn = rdapc.get_asn(data_normalize_autnum.name) + # uncomment to write expected data + with open(f"tests/data/normalize/autnum/{data_normalize_autnum.name}.expected", "w") as f: + f.write(json.dumps(asn.normalized, indent=2)) + + assert json.dumps(data_normalize_autnum.expected, indent=2) == json.dumps(asn.normalized, indent=2) + +@pytest_filedata.RequestsData("rdap") +def test_rdap_entity_lookup(rdapc, data_normalize_entity): + with patch("rdap.normalize.geo.lookup") as mock_lookup: + mock_lookup.side_effect = dynamic_mock_address + entity = rdapc.get_entity(data_normalize_entity.name) + # uncomment to write expected data + #with open(f"tests/data/normalize/entity/{data_normalize_entity.name}.expected", "w") as f: + # f.write(json.dumps(entity.normalized, indent=2)) + + assert json.dumps(data_normalize_entity.expected, indent=2) == json.dumps(entity.normalized, indent=2) + +@pytest_filedata.RequestsData("rdap") +def test_rdap_domain_lookup(rdapc, data_normalize_domain): + entity = rdapc.get_domain(data_normalize_domain.name) + # uncomment to write expected data + #with open(f"tests/data/normalize/domain/{data_normalize_domain.name}.expected", "w") as f: + # f.write(json.dumps(entity.normalized, indent=2)) + + assert json.dumps(data_normalize_domain.expected, indent=2) == json.dumps(entity.normalized, indent=2) + +@pytest_filedata.RequestsData("rdap") +def test_rdap_ip_lookup(rdapc, data_normalize_ip): + entity = rdapc.get_ip(data_normalize_ip.name) + # uncomment to write expected data + with open(f"tests/data/normalize/ip/{data_normalize_ip.name}.expected", "w") as f: + f.write(json.dumps(entity.normalized, indent=2)) + + assert json.dumps(data_normalize_ip.expected, indent=2) == json.dumps(entity.normalized, indent=2) + From 9474f1674fe02e147597b5646b47c810d7922070 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 25 Jul 2024 14:38:04 +0000 Subject: [PATCH 18/28] linting --- rdap/context.py | 39 ++++++++++++++++++++++++++++++++++++++- rdap/normalize/base.py | 2 -- rdap/schema/source.py | 36 ++++++++++++++++++++++++++++++++---- tests/test_normalize.py | 8 ++++---- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/rdap/context.py b/rdap/context.py index 0c6c887..f82eeb4 100644 --- a/rdap/context.py +++ b/rdap/context.py @@ -1,3 +1,7 @@ +""" +Contact management for RDAP requests. +""" + from contextvars import ContextVar import pydantic from datetime import datetime @@ -10,18 +14,43 @@ ] class RdapSource(pydantic.BaseModel): + """ + Describes a source of RDAP data. + """ + + # urls requested for this source urls: list[str] = pydantic.Field(default_factory=list) + + # rdap object handle handle: str | None = None + + # source creation date (if available) created: datetime | None = None + + # source last update date (if available) updated: datetime | None = None class RdapRequestState(pydantic.BaseModel): + """ + Describe the current rdap request, tracking sources queried + and entities retrieved. + """ + + # list of sources for the current request sources: list[RdapSource] = pydantic.Field(default_factory=list) - client: object | None = None #RdapClient + # reference to the rdap client instance + client: object | None = None + + # cache of entities (to avoid duplicate requests to the same entity + # within the current request context) entities: dict = pydantic.Field(default_factory=dict) def update_source(self, handle:str, created:datetime | None, updated:datetime | None): + """ + Update the current source with the handle and dates. + """ + self.sources[-1].handle = handle self.sources[-1].created = created self.sources[-1].updated = updated @@ -35,6 +64,14 @@ def update_source(self, handle:str, created:datetime | None, updated:datetime | class RdapRequestContext: + """ + Opens a request context + + If no state is present, a new state is created. + + If a state is present, a new source is added to the state. + """ + def __init__(self, url:str = None, client:object = None): self.url = url self.token = None diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 7649a65..8b24252 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -6,10 +6,8 @@ import phonenumbers from datetime import datetime -from rdap.exceptions import RdapHTTPError from rdap.schema.normalized import ( Contact, - Location, Nameserver, DNSSEC ) diff --git a/rdap/schema/source.py b/rdap/schema/source.py index 4ad1d57..62d904a 100644 --- a/rdap/schema/source.py +++ b/rdap/schema/source.py @@ -17,14 +17,42 @@ None: rdap, } -def ip_network_model(rir:str): +def ip_network_model(rir:str) -> rdap.IPNetwork: + """ + Returns pydantic model for IPNetwork for the given RIR + + Arguments: + + - rir: str: RIR name (e.g., "arin", "ripe", "apnic", "afrinic", "lacnic") + """ return SCHEMAS_BY_RIR[rir].IPNetwork -def domain_model(rir:str): +def domain_model(rir:str) -> rdap.Domain: + """ + Returns pydantic model for Domain for the given RIR + + Arguments: + + - rir: str: RIR name (e.g., "arin", "ripe", "apnic", "afrinic", "lacnic") + """ return SCHEMAS_BY_RIR[rir].Domain -def autnum_model(rir:str): +def autnum_model(rir:str) -> rdap.AutNum: + """ + Returns pydantic model for AutNum for the given RIR + + Arguments: + + - rir: str: RIR name (e.g., "arin", "ripe", "apnic", "afrinic", "lacnic") + """ return SCHEMAS_BY_RIR[rir].AutNum -def entity_model(rir:str): +def entity_model(rir:str) -> rdap.Entity: + """ + Returns pydantic model for Entity for the given RIR + + Arguments: + + - rir: str: RIR name (e.g., "arin", "ripe", "apnic", "afrinic", "lacnic") + """ return SCHEMAS_BY_RIR[rir].Entity diff --git a/tests/test_normalize.py b/tests/test_normalize.py index fe6e637..019df2c 100644 --- a/tests/test_normalize.py +++ b/tests/test_normalize.py @@ -29,8 +29,8 @@ def test_rdap_asn_lookup(rdapc, data_normalize_autnum): mock_lookup.side_effect = dynamic_mock_address asn = rdapc.get_asn(data_normalize_autnum.name) # uncomment to write expected data - with open(f"tests/data/normalize/autnum/{data_normalize_autnum.name}.expected", "w") as f: - f.write(json.dumps(asn.normalized, indent=2)) + #with open(f"tests/data/normalize/autnum/{data_normalize_autnum.name}.expected", "w") as f: + # f.write(json.dumps(asn.normalized, indent=2)) assert json.dumps(data_normalize_autnum.expected, indent=2) == json.dumps(asn.normalized, indent=2) @@ -58,8 +58,8 @@ def test_rdap_domain_lookup(rdapc, data_normalize_domain): def test_rdap_ip_lookup(rdapc, data_normalize_ip): entity = rdapc.get_ip(data_normalize_ip.name) # uncomment to write expected data - with open(f"tests/data/normalize/ip/{data_normalize_ip.name}.expected", "w") as f: - f.write(json.dumps(entity.normalized, indent=2)) + #with open(f"tests/data/normalize/ip/{data_normalize_ip.name}.expected", "w") as f: + # f.write(json.dumps(entity.normalized, indent=2)) assert json.dumps(data_normalize_ip.expected, indent=2) == json.dumps(entity.normalized, indent=2) From 2287069c7734df3037e346adff241492328d9532 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 25 Jul 2024 14:39:03 +0000 Subject: [PATCH 19/28] linting --- rdap/client.py | 4 +- rdap/context.py | 27 +++++---- rdap/normalize/__init__.py | 98 ++++++++++++++---------------- rdap/normalize/afrinic.py | 4 +- rdap/normalize/apnic.py | 8 ++- rdap/normalize/arin.py | 4 +- rdap/normalize/base.py | 119 ++++++++++++++++++++----------------- rdap/normalize/geo.py | 49 ++++++++------- rdap/normalize/lacnic.py | 4 +- rdap/normalize/ripe.py | 8 ++- rdap/objects.py | 9 ++- rdap/schema/normalized.py | 41 ++++++++++--- rdap/schema/rdap.py | 61 +++++++++++++------ rdap/schema/source.py | 12 ++-- tests/test_geo.py | 70 ++++++++++++---------- tests/test_normalize.py | 53 ++++++++++------- 16 files changed, 335 insertions(+), 236 deletions(-) diff --git a/rdap/client.py b/rdap/client.py index cc6fb55..73d2d6f 100644 --- a/rdap/client.py +++ b/rdap/client.py @@ -10,8 +10,8 @@ import rdap import rdap.bootstrap -from rdap.context import RdapRequestContext from rdap.config import Config +from rdap.context import RdapRequestContext from rdap.exceptions import RdapHTTPError, RdapNotFoundError from rdap.objects import RdapAsn, RdapDomain, RdapEntity, RdapNetwork @@ -321,7 +321,7 @@ def get_ip(self, address): query = f"/ip/{address}" return RdapNetwork(self._rdap_get(query).json(), self) - def get_entity(self, handle, base_url = None): + def get_entity(self, handle, base_url=None): """ get entity information in object form """ diff --git a/rdap/context.py b/rdap/context.py index f82eeb4..f0231bc 100644 --- a/rdap/context.py +++ b/rdap/context.py @@ -3,8 +3,10 @@ """ from contextvars import ContextVar -import pydantic from datetime import datetime + +import pydantic + from rdap.exceptions import RdapHTTPError __all__ = [ @@ -13,6 +15,7 @@ "RdapRequestState", ] + class RdapSource(pydantic.BaseModel): """ Describes a source of RDAP data. @@ -30,6 +33,7 @@ class RdapSource(pydantic.BaseModel): # source last update date (if available) updated: datetime | None = None + class RdapRequestState(pydantic.BaseModel): """ Describe the current rdap request, tracking sources queried @@ -46,7 +50,9 @@ class RdapRequestState(pydantic.BaseModel): # within the current request context) entities: dict = pydantic.Field(default_factory=dict) - def update_source(self, handle:str, created:datetime | None, updated:datetime | None): + def update_source( + self, handle: str, created: datetime | None, updated: datetime | None + ): """ Update the current source with the handle and dates. """ @@ -55,6 +61,7 @@ def update_source(self, handle:str, created:datetime | None, updated:datetime | self.sources[-1].created = created self.sources[-1].updated = updated + # context that holds the currently requested rdap url rdap_request = ContextVar("rdap_request", default=RdapRequestState()) @@ -62,8 +69,8 @@ def update_source(self, handle:str, created:datetime | None, updated:datetime | # context manager to set the rdap url # can be nested -class RdapRequestContext: +class RdapRequestContext: """ Opens a request context @@ -72,13 +79,13 @@ class RdapRequestContext: If a state is present, a new source is added to the state. """ - def __init__(self, url:str = None, client:object = None): + def __init__(self, url: str = None, client: object = None): self.url = url self.token = None self.client = client def __enter__(self): - + # get existing state state = rdap_request.get() @@ -86,7 +93,9 @@ def __enter__(self): if state and self.url: state.sources.append(RdapSource(urls=[self.url])) else: - state = RdapRequestState(sources=[RdapSource(urls=[self.url] if self.url else [])]) + state = RdapRequestState( + sources=[RdapSource(urls=[self.url] if self.url else [])] + ) self.token = rdap_request.set(state) if self.client: @@ -98,11 +107,11 @@ def __exit__(self, *exc): if self.token: rdap_request.reset(self.token) - def push_url(self, url:str): + def push_url(self, url: str): state = rdap_request.get() state.sources[-1].urls.append(url) - def get(self, typ:str, handle:str): + def get(self, typ: str, handle: str): state = rdap_request.get() client = state.client @@ -119,5 +128,3 @@ def get(self, typ:str, handle:str): except RdapHTTPError: state.entities[handle] = {} return {} - - diff --git a/rdap/normalize/__init__.py b/rdap/normalize/__init__.py index b433de0..16ef885 100644 --- a/rdap/normalize/__init__.py +++ b/rdap/normalize/__init__.py @@ -4,29 +4,23 @@ import json -import rdap.normalize.base as base import rdap.normalize.afrinic as afrinic import rdap.normalize.apnic as apnic import rdap.normalize.arin as arin +import rdap.normalize.base as base +import rdap.normalize.geo as geo import rdap.normalize.lacnic as lacnic import rdap.normalize.ripe as ripe - -import rdap.normalize.geo as geo - -import rdap.schema.normalized as schema - +import rdap.schema.normalized as schema import rdap.schema.rdap as rdap_schema - +from rdap.context import RdapRequestState, rdap_request from rdap.schema.source import ( autnum_model, + domain_model, entity_model, ip_network_model, - domain_model, ) -from rdap.context import rdap_request, RdapRequestState - - __all__ = [ "normalize", "normalize_autnum", @@ -43,32 +37,32 @@ "afrinic": afrinic.Handler(), "lacnic": lacnic.Handler(), # other (verisign for domains etc.) - None: base.Handler() + None: base.Handler(), } + def get_sources( - state: RdapRequestState, + state: RdapRequestState, handle: str, - entity: schema.Network | schema.IPNetwork | schema.Domain | schema.Entity + entity: schema.Network | schema.IPNetwork | schema.Domain | schema.Entity, ) -> list[schema.Source]: sources = [] - + for source in state.sources: if not source.urls or not source.handle: continue source = schema.Source( - handle = source.handle, - created = source.created, - updated = source.updated, - urls = source.urls, + handle=source.handle, + created=source.created, + updated=source.updated, + urls=source.urls, ) sources.append(source) - return sources @@ -78,7 +72,7 @@ def normalize(data: dict, rir: str, typ: str) -> dict: Will return a normalized dict based on the RIR """ - + if typ == "autnum": return normalize_autnum(data, rir) elif typ == "entity": @@ -90,6 +84,7 @@ def normalize(data: dict, rir: str, typ: str) -> dict: else: raise ValueError(f"Type {typ} not supported") + def normalize_autnum(data: dict, rir: str) -> dict: """ Normalize data based on RIR: Autnum @@ -105,21 +100,19 @@ def normalize_autnum(data: dict, rir: str) -> dict: rdap_autnum = autnum_model(rir)(**data) current_rdap_request.update_source( - rdap_autnum.handle, - **handler.dates(rdap_autnum.events) + rdap_autnum.handle, **handler.dates(rdap_autnum.events) ) org_name = handler.org_name(rdap_autnum) org = schema.Organization(name=org_name) - net = schema.Network( - name = rdap_autnum.name, - organization = org, - asn = rdap_autnum.startAutnum, - contacts = handler.contacts(rdap_autnum), - locations = handler.locations(rdap_autnum), - **handler.dates(rdap_autnum.events) + name=rdap_autnum.name, + organization=org, + asn=rdap_autnum.startAutnum, + contacts=handler.contacts(rdap_autnum), + locations=handler.locations(rdap_autnum), + **handler.dates(rdap_autnum.events), ) if current_rdap_request: @@ -127,6 +120,7 @@ def normalize_autnum(data: dict, rir: str) -> dict: return json.loads(net.model_dump_json()) + def normalize_ip(data: dict, rir: str) -> dict: """ Normalize data based on RIR: IPNetwork @@ -142,22 +136,21 @@ def normalize_ip(data: dict, rir: str) -> dict: rdap_ip_network = ip_network_model(rir)(**data) current_rdap_request.update_source( - rdap_ip_network.handle, - **handler.dates(rdap_ip_network.events) + rdap_ip_network.handle, **handler.dates(rdap_ip_network.events) ) prefix = handler.prefix(rdap_ip_network) net = schema.IPNetwork( - name = rdap_ip_network.name, - prefix = prefix, - parent= handler.parent_prefix(rdap_ip_network), - version = handler.ip_version(rdap_ip_network), - type = rdap_ip_network.type, + name=rdap_ip_network.name, + prefix=prefix, + parent=handler.parent_prefix(rdap_ip_network), + version=handler.ip_version(rdap_ip_network), + type=rdap_ip_network.type, # TODO: What happens if more than one status is in there? - status = rdap_ip_network.status[0] if rdap_ip_network.status else None, - contacts = handler.contacts(rdap_ip_network), - **handler.dates(rdap_ip_network.events) + status=rdap_ip_network.status[0] if rdap_ip_network.status else None, + contacts=handler.contacts(rdap_ip_network), + **handler.dates(rdap_ip_network.events), ) if current_rdap_request: @@ -165,6 +158,7 @@ def normalize_ip(data: dict, rir: str) -> dict: return json.loads(net.model_dump_json()) + def normalize_domain(data: dict, rir: str) -> dict: """ Normalize data based on RIR: Domain @@ -184,12 +178,12 @@ def normalize_domain(data: dict, rir: str) -> dict: ) net = schema.Domain( - name = rdap_domain.ldhName, - handle = rdap_domain.handle, - dns_sec = handler.secure_dns(rdap_domain), - contacts = handler.contacts(rdap_domain), - nameservers = handler.nameservers(rdap_domain), - **handler.dates(rdap_domain.events) + name=rdap_domain.ldhName, + handle=rdap_domain.handle, + dns_sec=handler.secure_dns(rdap_domain), + contacts=handler.contacts(rdap_domain), + nameservers=handler.nameservers(rdap_domain), + **handler.dates(rdap_domain.events), ) if current_rdap_request: @@ -224,14 +218,14 @@ def normalize_entity(data: dict, rir: str) -> dict: org = None entity = schema.Entity( - name = rdap_entity.handle, - organization = org, - contacts = handler.contacts_from_entity(rdap_entity), - locations = handler.locations_from_entity(rdap_entity), - **handler.dates(rdap_entity.events) + name=rdap_entity.handle, + organization=org, + contacts=handler.contacts_from_entity(rdap_entity), + locations=handler.locations_from_entity(rdap_entity), + **handler.dates(rdap_entity.events), ) if current_rdap_request: entity.sources = get_sources(current_rdap_request, rdap_entity.handle, entity) - return json.loads(entity.model_dump_json()) \ No newline at end of file + return json.loads(entity.model_dump_json()) diff --git a/rdap/normalize/afrinic.py b/rdap/normalize/afrinic.py index 995a1e1..833e5a2 100644 --- a/rdap/normalize/afrinic.py +++ b/rdap/normalize/afrinic.py @@ -8,8 +8,10 @@ "Handler", ] + class Handler(base.Handler): """ No known AFRINIC specific normalizations. """ - pass \ No newline at end of file + + pass diff --git a/rdap/normalize/apnic.py b/rdap/normalize/apnic.py index 0feaca9..d6b93f7 100644 --- a/rdap/normalize/apnic.py +++ b/rdap/normalize/apnic.py @@ -9,13 +9,15 @@ "Handler", ] -class Handler(base.Handler): +class Handler(base.Handler): """ APNIC sometimes puts org name into the remarks """ - def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + def org_name( + self, entity: schema.AutNum | schema.IPNetwork | schema.Domain + ) -> str | None: """ If super() return None or equal to entity.name try checking remarks for an entry where title == "description" @@ -32,4 +34,4 @@ def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> org_name = remark.description[0] break - return org_name \ No newline at end of file + return org_name diff --git a/rdap/normalize/arin.py b/rdap/normalize/arin.py index 43ceef1..c26dcb1 100644 --- a/rdap/normalize/arin.py +++ b/rdap/normalize/arin.py @@ -8,8 +8,10 @@ "Handler", ] + class Handler(base.Handler): """ No known ARIN specific normalizations. """ - pass \ No newline at end of file + + pass diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 8b24252..42b3eee 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -3,24 +3,20 @@ """ import ipaddress - -import phonenumbers from datetime import datetime -from rdap.schema.normalized import ( - Contact, - Nameserver, - DNSSEC -) -import rdap.normalize.geo as geo -from rdap.context import rdap_request, RdapRequestState, RdapRequestContext +import phonenumbers +import rdap.normalize.geo as geo import rdap.schema.rdap as schema +from rdap.context import RdapRequestContext, RdapRequestState, rdap_request +from rdap.schema.normalized import DNSSEC, Contact, Nameserver __all__ = [ "Handler", ] + class Handler: def locations_from_entity(self, entity: schema.Entity) -> list[str]: @@ -47,16 +43,16 @@ def locations_from_entity(self, entity: schema.Entity) -> list[str]: if entity.entities: for _entity in entity.entities: - locations.extend( - self.locations_from_entity(_entity) - ) + locations.extend(self.locations_from_entity(_entity)) # remove dupes locations = list(set(locations)) return locations - def locations(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> list[str]: + def locations( + self, entity: schema.AutNum | schema.IPNetwork | schema.Domain + ) -> list[str]: """ Will parse an address from an object @@ -77,8 +73,9 @@ def locations(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> return locations - def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[Contact]: - + def contacts_from_entity( + self, entity: schema.Entity, deep: bool = True + ) -> list[Contact]: """ Will parse contacts from an entity @@ -100,9 +97,9 @@ def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[ for vcard in entity.vcardArray[1:]: contact = Contact( - name = "", - roles = getattr(entity, "roles", []) or [], - **self.dates(entity.events) + name="", + roles=getattr(entity, "roles", []) or [], + **self.dates(entity.events), ) for vcard_entry in vcard: if vcard_entry[0] == "fn": @@ -114,8 +111,7 @@ def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[ try: phone_number = phonenumbers.parse(contact.phone, None) contact.phone = phonenumbers.format_number( - phone_number, - phonenumbers.PhoneNumberFormat.INTERNATIONAL + phone_number, phonenumbers.PhoneNumberFormat.INTERNATIONAL ) except phonenumbers.phonenumberutil.NumberParseException: # TODO: setting to allow for invalid phone numbers? @@ -135,8 +131,11 @@ def contacts_from_entity(self, entity: schema.Entity, deep:bool = True) -> list[ return contacts - def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, deep:bool = True) -> list[Contact]: - + def contacts( + self, + entity: schema.AutNum | schema.IPNetwork | schema.Domain, + deep: bool = True, + ) -> list[Contact]: """ Will parse contacts from an object @@ -168,14 +167,21 @@ def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, dee if key in combined_contacts: combined_contacts[key].roles.extend(_contact.roles) - if not combined_contacts[key].email and _contact.email and _contact.phone == combined_contacts[key].phone: + if ( + not combined_contacts[key].email + and _contact.email + and _contact.phone == combined_contacts[key].phone + ): combined_contacts[key].email = _contact.email - if not combined_contacts[key].phone and _contact.phone and _contact.email == combined_contacts[key].email: + if ( + not combined_contacts[key].phone + and _contact.phone + and _contact.email == combined_contacts[key].email + ): combined_contacts[key].phone = _contact.phone else: combined_contacts[key] = _contact - contacts = list(combined_contacts.values()) # sort roles @@ -184,8 +190,10 @@ def contacts(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain, dee return contacts - def recurse_contacts(self, entity: schema.Entity, contacts: list[Contact], roles: list[str]) -> list[Contact]: - + def recurse_contacts( + self, entity: schema.Entity, contacts: list[Contact], roles: list[str] + ) -> list[Contact]: + request_state: RdapRequestState = rdap_request.get() client = request_state.client @@ -198,10 +206,12 @@ def recurse_contacts(self, entity: schema.Entity, contacts: list[Contact], roles if not client.recurse_roles.isdisjoint(roles): with RdapRequestContext(url=handle_url, client=client) as ctx: - contacts.extend([ - Contact(**contact) for contact in ctx.get("entity", entity.handle)["contacts"] - ]) - + contacts.extend( + [ + Contact(**contact) + for contact in ctx.get("entity", entity.handle)["contacts"] + ] + ) def org_name_from_entity(self, entity: schema.Entity) -> str | None: """ @@ -224,13 +234,15 @@ def org_name_from_entity(self, entity: schema.Entity) -> str | None: kind_is_org = True if vcard_entry[0] == "fn": org_name = vcard_entry[3] - + if kind_is_org and org_name: return org_name return None - def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + def org_name( + self, entity: schema.AutNum | schema.IPNetwork | schema.Domain + ) -> str | None: """ Will parse an org name from an object @@ -245,8 +257,9 @@ def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> return entity.name or None - - def prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network: + def prefix( + self, ip_network: schema.IPNetwork + ) -> ipaddress.IPv4Network | ipaddress.IPv6Network: """ Will return the CIDR of an IPNetwork object "cidr0_cidrs" : [ { @@ -271,7 +284,7 @@ def prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddr if "v4prefix" in cidr: return ipaddress.IPv4Network(f"{cidr['v4prefix']}/{cidr['length']}") - + if "v6prefix" in cidr: return ipaddress.IPv6Network(f"{cidr['v6prefix']}/{cidr['length']}") @@ -296,7 +309,9 @@ def ip_version(self, ip_network: schema.IPNetwork) -> int | None: return None - def parent_prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: + def parent_prefix( + self, ip_network: schema.IPNetwork + ) -> ipaddress.IPv4Network | ipaddress.IPv6Network | None: """ Parent network prefix from `parentHandle` @@ -307,18 +322,18 @@ def parent_prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | return None # Extract the IP address part from the parentHandle - ip_parts = ip_network.parentHandle.split('-')[1:-1] - + ip_parts = ip_network.parentHandle.split("-")[1:-1] + # Reconstruct the IP address string - ip_str = '.'.join(ip_parts) - + ip_str = ".".join(ip_parts) + # Determine the appropriate prefix length based on the number of non-zero octets - non_zero_octets = sum(1 for part in ip_parts if part != '0') + non_zero_octets = sum(1 for part in ip_parts if part != "0") prefix_length = non_zero_octets * 8 - + # Construct the CIDR notation cidr = f"{ip_str}/{prefix_length}" - + try: # Attempt to create an IP network object return ipaddress.ip_network(cidr, strict=False) @@ -326,7 +341,6 @@ def parent_prefix(self, ip_network: schema.IPNetwork) -> ipaddress.IPv4Network | # If the IP address is invalid, return None return None - def secure_dns(self, domain: schema.Domain) -> DNSSEC: """ Will determine if the domain has secure DNS @@ -343,7 +357,10 @@ def secure_dns(self, domain: schema.Domain) -> DNSSEC: if not domain.secureDNS: return DNSSEC.unknown - if domain.secureDNS.delegationSigned is None and domain.secureDNS.zeroSigned is None: + if ( + domain.secureDNS.delegationSigned is None + and domain.secureDNS.zeroSigned is None + ): return DNSSEC.unknown if domain.secureDNS.delegationSigned or domain.secureDNS.zeroSigned: @@ -351,7 +368,6 @@ def secure_dns(self, domain: schema.Domain) -> DNSSEC: return DNSSEC.insecure - def nameservers(self, domain: schema.Domain) -> list[Nameserver]: """ Returns normalized nameservers from a domain object @@ -365,7 +381,6 @@ def nameservers(self, domain: schema.Domain) -> list[Nameserver]: return nameservers def dates(self, events: list[schema.Event]) -> dict[str, str]: - """ Return the created and updated dates from the events """ @@ -379,17 +394,13 @@ def dates(self, events: list[schema.Event]) -> dict[str, str]: if event.eventAction == "last changed": updated: datetime = event.eventDate - # set utc timezone if no timezone is present # created and du if created and not created.tzinfo: created = created.replace(tzinfo=datetime.timezone.utc) - + if updated and not updated.tzinfo: updated = updated.replace(tzinfo=datetime.timezone.utc) - return { - "created": created, - "updated": updated - } \ No newline at end of file + return {"created": created, "updated": updated} diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index 8dbe6f2..f20ce79 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -4,27 +4,33 @@ """ import os +from datetime import datetime + import googlemaps -from datetime import datetime -from rdap.schema.normalized import Location, GeoLocation -from rdap.context import rdap_request, RdapRequestState +from rdap.context import RdapRequestState, rdap_request +from rdap.schema.normalized import GeoLocation, Location GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY") + class RequestError(Exception): pass + class Timeout(Exception): pass + class NotFound(KeyError): pass + class GoogleKeyNotSet(Exception): pass -def get_client(key:str = GOOGLE_MAPS_API_KEY): + +def get_client(key: str = GOOGLE_MAPS_API_KEY): if not key: raise GoogleKeyNotSet("Google Maps API Key not set") @@ -32,7 +38,7 @@ def get_client(key:str = GOOGLE_MAPS_API_KEY): return googlemaps.Client(key) -def lookup(formatted_address:str, client = None) -> dict: +def lookup(formatted_address: str, client=None) -> dict: """ Return the latitude, longitude field values of the specified location. @@ -70,8 +76,8 @@ def lookup(formatted_address:str, client = None) -> dict: return result[0] -def normalize(formatted_address:str, date:datetime = None, client=None) -> Location: +def normalize(formatted_address: str, date: datetime = None, client=None) -> Location: """ Takes a formatted address and returns a normalized location object """ @@ -84,8 +90,8 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat # only the address field set print("EXC", exc) return Location( - updated = date, - address = formatted_address, + updated=date, + address=formatted_address, ) city = None @@ -102,7 +108,7 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat print("Component:", component) if "country" in types: country = component.get("short_name") - + if "postal_code" in types: postal_code = component.get("long_name") @@ -114,18 +120,17 @@ def normalize(formatted_address:str, date:datetime = None, client=None) -> Locat if "subpremise" in types: suite = component.get("long_name") - - + return Location( - updated = date, - country = country, - city = city, - postal_code = postal_code, - floor = floor, - suite = suite, - address = result.get("formatted_address"), - geo = GeoLocation( - latitude = result.get("geometry").get("location").get("lat"), - longitude = result.get("geometry").get("location").get("lng") + updated=date, + country=country, + city=city, + postal_code=postal_code, + floor=floor, + suite=suite, + address=result.get("formatted_address"), + geo=GeoLocation( + latitude=result.get("geometry").get("location").get("lat"), + longitude=result.get("geometry").get("location").get("lng"), ), - ) \ No newline at end of file + ) diff --git a/rdap/normalize/lacnic.py b/rdap/normalize/lacnic.py index 29e72b3..d80b41f 100644 --- a/rdap/normalize/lacnic.py +++ b/rdap/normalize/lacnic.py @@ -8,8 +8,10 @@ "Handler", ] + class Handler(base.Handler): """ No known LACNIC specific normalizations. """ - pass \ No newline at end of file + + pass diff --git a/rdap/normalize/ripe.py b/rdap/normalize/ripe.py index f9217c5..8720b13 100644 --- a/rdap/normalize/ripe.py +++ b/rdap/normalize/ripe.py @@ -9,13 +9,15 @@ "Handler", ] -class Handler(base.Handler): +class Handler(base.Handler): """ RIPE sometimes puts org name into the remarks """ - def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> str | None: + def org_name( + self, entity: schema.AutNum | schema.IPNetwork | schema.Domain + ) -> str | None: """ If super() return None or equal to entity.name try checking remarks for an entry where title == "description" @@ -29,4 +31,4 @@ def org_name(self, entity: schema.AutNum | schema.IPNetwork | schema.Domain) -> org_name = remark.description[0] break - return org_name \ No newline at end of file + return org_name diff --git a/rdap/objects.py b/rdap/objects.py index a750d2e..d0b51bb 100644 --- a/rdap/objects.py +++ b/rdap/objects.py @@ -1,7 +1,7 @@ from rdap.exceptions import RdapHTTPError, RdapNotFoundError - from rdap.normalize import normalize + def rir_from_domain(domain): """Gets the RIR from a URL or domain, if possible""" try: @@ -210,6 +210,7 @@ def __init__(self, data, rdapc=None): def normalized(self) -> dict: return normalize(self._data, self.get_rir(), "autnum") + class RdapNetwork(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) @@ -218,6 +219,7 @@ def __init__(self, data, rdapc=None): def normalized(self) -> dict: return normalize(self._data, self.get_rir(), "ip") + class RdapDomain(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) @@ -225,11 +227,12 @@ def __init__(self, data, rdapc=None): @property def normalized(self) -> dict: return normalize(self._data, self.get_rir(), "domain") - + + class RdapEntity(RdapObject): def __init__(self, data, rdapc=None): super().__init__(data, rdapc) @property def normalized(self) -> dict: - return normalize(self._data, self.get_rir(), "entity") \ No newline at end of file + return normalize(self._data, self.get_rir(), "entity") diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 42798b3..9d11c0f 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -2,11 +2,12 @@ Pydantic schemas for normalized RDAP data """ +import enum +import ipaddress from datetime import datetime from typing import Any + import pydantic -import enum -import ipaddress __all__ = [ "IP_VERSION", @@ -22,25 +23,30 @@ "IPNetwork", "Entity", "Nameserver", - "Domain" + "Domain", ] + class IP_VERSION(int, enum.Enum): """ Enum for IP version """ + ipv4 = 4 ipv6 = 6 + class STATUS(str, enum.Enum): active = "active" inactive = "inactive" + NORMALIZED_STATUS = { "administrative": "active", "validated": "active", } + class ROLE(str, enum.Enum): abuse = "abuse" admin = "admin" @@ -48,28 +54,34 @@ class ROLE(str, enum.Enum): technical = "technical" registrant = "registrant" + NORMALIZED_ROLES = { "administrative": "admin", "noc": "technical", - "registrar": "registrant" + "registrar": "registrant", } + class DNSSEC(str, enum.Enum): secure = "secure" insecure = "insecure" unknown = "unknown" + class GeoLocation(pydantic.BaseModel): """ Describes geographic coordinates """ + latitude: float longitude: float + class Location(pydantic.BaseModel): """ Describes a location """ + updated: datetime | None = None country: str | None = None city: str | None = None @@ -82,10 +94,12 @@ class Location(pydantic.BaseModel): def __hash__(self): return f"{self.address}-{self.city}-{self.country}-{self.postal_code}-{self.floor}-{self.suite}".__hash__() + class Contact(pydantic.BaseModel): """ Describes a point of contact """ + created: datetime | None = None updated: datetime | None = None name: str @@ -96,13 +110,13 @@ class Contact(pydantic.BaseModel): @pydantic.model_validator(mode="before") @classmethod def normalize_roles(cls, data: Any) -> Any: - + roles = [] for role in data.get("roles", []): role = NORMALIZED_ROLES.get(role, role) roles.append(role) - + data["roles"] = roles # drop duplicates @@ -111,16 +125,17 @@ def normalize_roles(cls, data: Any) -> Any: return data - def __hash__(self): return f"{self.name}-{self.email}-{self.phone}: {self.roles}".__hash__() + class Source(pydantic.BaseModel): """ Describes a source of rdap data Will contain where the data was fetched from and when """ + created: datetime | None = None updated: datetime | None = None handle: str @@ -132,12 +147,15 @@ class Organization(pydantic.BaseModel): """ Describes an organization """ + name: str + class Network(pydantic.BaseModel): """ Describes a network """ + created: datetime | None = None updated: datetime | None = None asn: int @@ -152,6 +170,7 @@ class IPNetwork(pydantic.BaseModel): """ Describes an IP network """ + created: datetime | None = None updated: datetime | None = None prefix: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None @@ -174,10 +193,12 @@ def normalize_status(cls, data: Any) -> Any: return data + class Entity(pydantic.BaseModel): """ Describes an entity """ + created: datetime | None = None updated: datetime | None = None name: str @@ -186,16 +207,20 @@ class Entity(pydantic.BaseModel): contacts: list[Contact] = pydantic.Field(default_factory=list) sources: list[Source] = pydantic.Field(default_factory=list) + class Nameserver(pydantic.BaseModel): """ Describes a nameserver """ + host: str + class Domain(pydantic.BaseModel): """ Describes a domain """ + created: datetime | None = None updated: datetime | None = None name: str @@ -203,4 +228,4 @@ class Domain(pydantic.BaseModel): dns_sec: DNSSEC nameservers: list[Nameserver] = pydantic.Field(default_factory=list) contacts: list[Contact] = pydantic.Field(default_factory=list) - sources: list[Source] = pydantic.Field(default_factory=list) \ No newline at end of file + sources: list[Source] = pydantic.Field(default_factory=list) diff --git a/rdap/schema/rdap.py b/rdap/schema/rdap.py index 60bfb5e..1ea9409 100644 --- a/rdap/schema/rdap.py +++ b/rdap/schema/rdap.py @@ -1,20 +1,22 @@ -from pydantic import BaseModel, Field from datetime import datetime from typing import Any -__all__ = [ - 'Link', - 'Event', - 'Notice', - 'VCardValue', - 'Remark', - 'Entity', - 'IPNetwork', +from pydantic import BaseModel, Field +__all__ = [ + "Link", + "Event", + "Notice", + "VCardValue", + "Remark", + "Entity", + "IPNetwork", ] + class Link(BaseModel): """Represents a hyperlink in the RDAP response.""" + # The label or description of the link value: str | None = None # The relationship of the link to the current object @@ -24,15 +26,19 @@ class Link(BaseModel): # The URL of the link href: str | None = None + class Event(BaseModel): """Represents a timestamped event in the lifecycle of an RDAP object.""" + # The type of event (e.g., "registration", "last changed") eventAction: str | None = None # The date and time of the event eventDate: datetime | None = None + class Notice(BaseModel): """Represents a notice or message in the RDAP response.""" + # The title of the notice title: str | None = None # A list of text lines comprising the notice @@ -40,24 +46,32 @@ class Notice(BaseModel): # Optional links related to the notice links: list[Link] = Field(default_factory=list) + class VCardValue(BaseModel): """Represents additional properties for vCard values.""" + # Types associated with the vCard value (e.g., "work", "voice" for telephone) type: list[str] | None = None + class Remark(BaseModel): """Represents a remark or comment in the RDAP response.""" + # The title of the remark title: str | None = None # A list of text lines comprising the remark description: list[str] = Field(default_factory=list) + class Entity(BaseModel): """Represents an entity (organization, individual, or role) in the RDAP response.""" + # A unique identifier for the entity handle: str = Field(default_factory=str) # Contact information in vCard format - vcardArray: list[str | list[list[str | dict | list | None]]] = Field(default_factory=list) + vcardArray: list[str | list[list[str | dict | list | None]]] = Field( + default_factory=list + ) # Roles of the entity (e.g., registrant, technical, administrative) roles: list[str] = Field(default_factory=list) # Links related to the entity @@ -73,20 +87,20 @@ class Entity(BaseModel): # Additional remarks about the entity remarks: list[Remark] = Field(default_factory=list) # Nested entities (e.g., contacts within an organization) - entities: list['Entity'] = Field(default_factory=list) - + entities: list["Entity"] = Field(default_factory=list) @property def self_link(self) -> str | None: """Returns the href of the link where rel == 'self'""" for link in self.links: - if link.rel == 'self': + if link.rel == "self": return link.href return None class IPNetwork(BaseModel): """Represents an IP network in the RDAP response.""" + # list of conformance levels rdapConformance: list[str] = Field(default_factory=list) # Notices related to the IP network @@ -108,11 +122,11 @@ class IPNetwork(BaseModel): # Additional remarks about the network remarks: list[Remark] = Field(default_factory=list) # Events associated with the network - events: list[Event]= Field(default_factory=list) + events: list[Event] = Field(default_factory=list) # Links related to the network - links: list[Link]= Field(default_factory=list) + links: list[Link] = Field(default_factory=list) # Entities associated with the network - entities: list[Entity]= Field(default_factory=list) + entities: list[Entity] = Field(default_factory=list) # WHOIS server for the network port43: str | None = None # Status of the network @@ -120,12 +134,14 @@ class IPNetwork(BaseModel): # Type of the object (always "ip network" for IPNetwork) objectClassName: str | None = None # CIDR notation for the network - cidr0_cidrs: list[dict]= Field(default_factory=list) + cidr0_cidrs: list[dict] = Field(default_factory=list) # Origin AS numbers for the network arin_originas0_originautnums: list = Field(default_factory=list) + class DSData(BaseModel): """Represents DS data for secure DNS in the RDAP response.""" + # Key tag for the DS record keyTag: int | None = None # Algorithm number for the DS record @@ -135,6 +151,7 @@ class DSData(BaseModel): # Digest value for the DS record digest: str | None = None + class SecureDNS(BaseModel): # true if there are DS records in the parent, false otherwise. delegationSigned: bool | None = None @@ -143,6 +160,7 @@ class SecureDNS(BaseModel): # DS data for secure DNS dsData: list[DSData] = Field(default_factory=list) + class Nameserver(BaseModel): objectClassName: str | None = None ldhName: str | None = None @@ -152,8 +170,10 @@ class Nameserver(BaseModel): port43: str | None = None events: list[Event] = Field(default_factory=list) + class Domain(BaseModel): """Represents a domain name in the RDAP response.""" + # list of conformance levels rdapConformance: list[str] = Field(default_factory=list) # Notices related to the domain @@ -179,8 +199,10 @@ class Domain(BaseModel): nameservers: list[Nameserver] = Field(default_factory=list) + class AutNum(BaseModel): """Represents an Autonomous System Number in the RDAP response.""" + # list of conformance levels rdapConformance: list[str] = Field(default_factory=list) # Notices related to the AS number @@ -207,8 +229,9 @@ class AutNum(BaseModel): entities: list[Entity] = Field(default_factory=list) # Status of the AS number status: list[str] = Field(default_factory=list) - + # Remarks about the AS number remarks: list[Remark] = Field(default_factory=list) -Entity.model_rebuild() \ No newline at end of file + +Entity.model_rebuild() diff --git a/rdap/schema/source.py b/rdap/schema/source.py index 62d904a..d5de705 100644 --- a/rdap/schema/source.py +++ b/rdap/schema/source.py @@ -17,7 +17,8 @@ None: rdap, } -def ip_network_model(rir:str) -> rdap.IPNetwork: + +def ip_network_model(rir: str) -> rdap.IPNetwork: """ Returns pydantic model for IPNetwork for the given RIR @@ -27,7 +28,8 @@ def ip_network_model(rir:str) -> rdap.IPNetwork: """ return SCHEMAS_BY_RIR[rir].IPNetwork -def domain_model(rir:str) -> rdap.Domain: + +def domain_model(rir: str) -> rdap.Domain: """ Returns pydantic model for Domain for the given RIR @@ -37,7 +39,8 @@ def domain_model(rir:str) -> rdap.Domain: """ return SCHEMAS_BY_RIR[rir].Domain -def autnum_model(rir:str) -> rdap.AutNum: + +def autnum_model(rir: str) -> rdap.AutNum: """ Returns pydantic model for AutNum for the given RIR @@ -47,7 +50,8 @@ def autnum_model(rir:str) -> rdap.AutNum: """ return SCHEMAS_BY_RIR[rir].AutNum -def entity_model(rir:str) -> rdap.Entity: + +def entity_model(rir: str) -> rdap.Entity: """ Returns pydantic model for Entity for the given RIR diff --git a/tests/test_geo.py b/tests/test_geo.py index 565be8d..edc640d 100644 --- a/tests/test_geo.py +++ b/tests/test_geo.py @@ -1,61 +1,61 @@ -import pytest -from unittest.mock import patch from datetime import datetime -from rdap.schema.normalized import Location, GeoLocation +from unittest.mock import patch + +import pytest + from rdap.context import RdapRequestState # Import the functions and exceptions from your module -from rdap.normalize.geo import ( - normalize, NotFound, GoogleKeyNotSet -) +from rdap.normalize.geo import GoogleKeyNotSet, NotFound, normalize +from rdap.schema.normalized import GeoLocation, Location # Mock data -MOCK_GEOCODE_RESULT = [{ - "formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", - "geometry": { - "location": { - "lat": 37.4224764, - "lng": -122.0842499 - } - }, - "address_components": [ - {"types": ["country"], "short_name": "US"}, - {"types": ["postal_code"], "long_name": "94043"}, - {"types": ["locality"], "long_name": "Mountain View"}, - {"types": ["floor"], "long_name": "4"}, - {"types": ["subpremise"], "long_name": "Suite 200"}, - ] -}] +MOCK_GEOCODE_RESULT = [ + { + "formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", + "geometry": {"location": {"lat": 37.4224764, "lng": -122.0842499}}, + "address_components": [ + {"types": ["country"], "short_name": "US"}, + {"types": ["postal_code"], "long_name": "94043"}, + {"types": ["locality"], "long_name": "Mountain View"}, + {"types": ["floor"], "long_name": "4"}, + {"types": ["subpremise"], "long_name": "Suite 200"}, + ], + } +] + @pytest.fixture def mock_google_client(): - with patch('googlemaps.Client') as mock_client: + with patch("googlemaps.Client") as mock_client: yield mock_client + @pytest.fixture def mock_rdap_request(): - with patch('rdap.context.rdap_request') as mock_request: + with patch("rdap.context.rdap_request") as mock_request: mock_request.get.return_value = RdapRequestState() yield mock_request + @pytest.fixture(autouse=True) def mock_google_api_key(): - with patch.dict('os.environ', {'GOOGLE_MAPS_API_KEY': 'fake_api_key'}): + with patch.dict("os.environ", {"GOOGLE_MAPS_API_KEY": "fake_api_key"}): yield + def test_normalize_success(mock_google_client): mock_client = mock_google_client.return_value mock_client.geocode.return_value = MOCK_GEOCODE_RESULT date = datetime.now() - + # Mock the lookup function directly - with patch('rdap.normalize.geo.lookup') as mock_lookup: + with patch("rdap.normalize.geo.lookup") as mock_lookup: # Return the first item of the list - mock_lookup.return_value = MOCK_GEOCODE_RESULT[0] + mock_lookup.return_value = MOCK_GEOCODE_RESULT[0] result = normalize("1600 Amphitheatre Parkway, Mountain View, CA", date) - assert isinstance(result, Location) assert result.updated == date assert result.country == "US" @@ -69,10 +69,13 @@ def test_normalize_success(mock_google_client): assert result.geo.longitude == -122.0842499 # Verify that lookup was called - mock_lookup.assert_called_once_with("1600 Amphitheatre Parkway, Mountain View, CA", None) + mock_lookup.assert_called_once_with( + "1600 Amphitheatre Parkway, Mountain View, CA", None + ) + def test_normalize_google_key_not_set(): - with patch('rdap.normalize.geo.lookup', side_effect=GoogleKeyNotSet): + with patch("rdap.normalize.geo.lookup", side_effect=GoogleKeyNotSet): date = datetime.now() result = normalize("1600 Amphitheatre Parkway, Mountain View, CA", date) @@ -86,8 +89,9 @@ def test_normalize_google_key_not_set(): assert result.suite is None assert result.geo is None + def test_normalize_not_found(): - with patch('rdap.normalize.geo.lookup', side_effect=NotFound): + with patch("rdap.normalize.geo.lookup", side_effect=NotFound): date = datetime.now() result = normalize("Non-existent Address", date) @@ -99,4 +103,4 @@ def test_normalize_not_found(): assert result.postal_code is None assert result.floor is None assert result.suite is None - assert result.geo is None \ No newline at end of file + assert result.geo is None diff --git a/tests/test_normalize.py b/tests/test_normalize.py index 019df2c..e557f1b 100644 --- a/tests/test_normalize.py +++ b/tests/test_normalize.py @@ -1,12 +1,14 @@ -import pytest -import os -import json -import pytest_filedata import copy +import json +import os from unittest.mock import patch +import pytest +import pytest_filedata + from tests.test_geo import MOCK_GEOCODE_RESULT + def dynamic_mock_address(formatted_address, client): result = copy.deepcopy(MOCK_GEOCODE_RESULT[0]) result["formatted_address"] = f"{formatted_address}" @@ -15,13 +17,14 @@ def dynamic_mock_address(formatted_address, client): @pytest.fixture(autouse=True) def set_google_maps_api_key(): - original_key = os.environ.get('GOOGLE_MAPS_API_KEY') - os.environ['GOOGLE_MAPS_API_KEY'] = 'your_test_api_key' + original_key = os.environ.get("GOOGLE_MAPS_API_KEY") + os.environ["GOOGLE_MAPS_API_KEY"] = "your_test_api_key" yield if original_key is not None: - os.environ['GOOGLE_MAPS_API_KEY'] = original_key + os.environ["GOOGLE_MAPS_API_KEY"] = original_key else: - del os.environ['GOOGLE_MAPS_API_KEY'] + del os.environ["GOOGLE_MAPS_API_KEY"] + @pytest_filedata.RequestsData("rdap") def test_rdap_asn_lookup(rdapc, data_normalize_autnum): @@ -29,10 +32,13 @@ def test_rdap_asn_lookup(rdapc, data_normalize_autnum): mock_lookup.side_effect = dynamic_mock_address asn = rdapc.get_asn(data_normalize_autnum.name) # uncomment to write expected data - #with open(f"tests/data/normalize/autnum/{data_normalize_autnum.name}.expected", "w") as f: + # with open(f"tests/data/normalize/autnum/{data_normalize_autnum.name}.expected", "w") as f: # f.write(json.dumps(asn.normalized, indent=2)) - - assert json.dumps(data_normalize_autnum.expected, indent=2) == json.dumps(asn.normalized, indent=2) + + assert json.dumps(data_normalize_autnum.expected, indent=2) == json.dumps( + asn.normalized, indent=2 + ) + @pytest_filedata.RequestsData("rdap") def test_rdap_entity_lookup(rdapc, data_normalize_entity): @@ -40,26 +46,33 @@ def test_rdap_entity_lookup(rdapc, data_normalize_entity): mock_lookup.side_effect = dynamic_mock_address entity = rdapc.get_entity(data_normalize_entity.name) # uncomment to write expected data - #with open(f"tests/data/normalize/entity/{data_normalize_entity.name}.expected", "w") as f: + # with open(f"tests/data/normalize/entity/{data_normalize_entity.name}.expected", "w") as f: # f.write(json.dumps(entity.normalized, indent=2)) - - assert json.dumps(data_normalize_entity.expected, indent=2) == json.dumps(entity.normalized, indent=2) + + assert json.dumps(data_normalize_entity.expected, indent=2) == json.dumps( + entity.normalized, indent=2 + ) + @pytest_filedata.RequestsData("rdap") def test_rdap_domain_lookup(rdapc, data_normalize_domain): entity = rdapc.get_domain(data_normalize_domain.name) # uncomment to write expected data - #with open(f"tests/data/normalize/domain/{data_normalize_domain.name}.expected", "w") as f: + # with open(f"tests/data/normalize/domain/{data_normalize_domain.name}.expected", "w") as f: # f.write(json.dumps(entity.normalized, indent=2)) - - assert json.dumps(data_normalize_domain.expected, indent=2) == json.dumps(entity.normalized, indent=2) + + assert json.dumps(data_normalize_domain.expected, indent=2) == json.dumps( + entity.normalized, indent=2 + ) + @pytest_filedata.RequestsData("rdap") def test_rdap_ip_lookup(rdapc, data_normalize_ip): entity = rdapc.get_ip(data_normalize_ip.name) # uncomment to write expected data - #with open(f"tests/data/normalize/ip/{data_normalize_ip.name}.expected", "w") as f: + # with open(f"tests/data/normalize/ip/{data_normalize_ip.name}.expected", "w") as f: # f.write(json.dumps(entity.normalized, indent=2)) - - assert json.dumps(data_normalize_ip.expected, indent=2) == json.dumps(entity.normalized, indent=2) + assert json.dumps(data_normalize_ip.expected, indent=2) == json.dumps( + entity.normalized, indent=2 + ) From a5e314e9e6ca07eb9f32a65b611d1b8c7ab72134 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Tue, 30 Jul 2024 14:55:25 +0000 Subject: [PATCH 20/28] api docs --- docs/api.md | 116 ++++++++++++++++++++++++++++++++++++++++++ rdap/normalize/geo.py | 1 - 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..dbbaee5 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,116 @@ +# API + +## Retrieve objects + +```python +import rdap + +# instantiate client +client = rdap.RdapClient() + +# asn +as63311 = client.get_asn(63311) + +# domain +domain = client.get_domain('example.com') + +# ip +ip = client.get_ip('206.41.110.0') + +# entity +entity = client.get_entity('NETWO7047-ARIN') +``` + +## Output normalized data (ASN example) + +```python +import json +import rdap + +# instantiate client +client = rdap.RdapClient() + +# request asn +as63311 = client.get_asn(63311) + +# normalized dict +print(json.dumps(as63311.normalized, indent=2)) +``` + +Output: +```json +{ + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "asn": 63311, + "name": "20C", + "organization": { + "name": "20C, LLC" + }, + "locations": [ + { + "updated": "2014-08-05T15:21:11-04:00", + "country": null, + "city": null, + "postal_code": null, + "address": "303 W Ohio #1701\nChicago\nIL\n60654\nUnited States", + "geo": null, + "floor": null, + "suite": null + }, + { + "updated": "2023-08-02T14:15:09-04:00", + "country": null, + "city": null, + "postal_code": null, + "address": "603 Discovery Dr\nWest Chicago\nIL\n60185\nUnited States", + "geo": null, + "floor": null, + "suite": null + } + ], + "contacts": [ + { + "created": "2014-07-03T23:22:49-04:00", + "updated": "2023-08-02T14:15:09-04:00", + "name": "Network Engineers", + "roles": [ + "abuse", + "admin", + "technical" + ], + "phone": "+1 978-636-0020", + "email": "neteng@20c.com" + } + ], + "sources": [ + { + "created": "2014-11-17T14:28:43-05:00", + "updated": "2018-10-24T22:58:16-04:00", + "handle": "AS63311", + "urls": [ + "https://rdap.org/autnum/63311", + "https://rdap.arin.net/registry/autnum/63311" + ], + "description": null + } + ] +} +``` + +## Work with normalized data through pydantic models + +```python +import rdap + +from rdap.schema.normalized import Network + +# instantiate client +client = rdap.RdapClient() + +# request asn +as63311 = Network(**client.get_asn(63311).normalized) + +for contact in as63311.contacts: + print(contact.name, contact.email) +``` \ No newline at end of file diff --git a/rdap/normalize/geo.py b/rdap/normalize/geo.py index f20ce79..8e24e54 100644 --- a/rdap/normalize/geo.py +++ b/rdap/normalize/geo.py @@ -88,7 +88,6 @@ def normalize(formatted_address: str, date: datetime = None, client=None) -> Loc # If a google maps key is not set, return a location object with # only the address field set - print("EXC", exc) return Location( updated=date, address=formatted_address, From fb343a386424d639e558383b24c2d9377eb0c6e3 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 1 Aug 2024 14:16:57 +0000 Subject: [PATCH 21/28] fix issue with contact sort --- rdap/normalize/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 42b3eee..09519e9 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -160,7 +160,7 @@ def contacts( combined_contacts = {} # sort by name and email - contacts = sorted(contacts, key=lambda x: (x.name, x.email)) + contacts = sorted(contacts, key=lambda x: (x.name or "", x.email or "")) for _contact in contacts: key = f"{_contact.name}" From a20f3f09491a28649ea9998be1130b6eb006cca2 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 1 Aug 2024 14:31:15 +0000 Subject: [PATCH 22/28] fix issue with contact recursion --- rdap/normalize/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 09519e9..2e8625e 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -209,7 +209,7 @@ def recurse_contacts( contacts.extend( [ Contact(**contact) - for contact in ctx.get("entity", entity.handle)["contacts"] + for contact in ctx.get("entity", entity.handle).get("contacts", []) ] ) From b9e275cdd0907aa5af8b67ed201523ec32595242 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 1 Aug 2024 16:54:53 +0000 Subject: [PATCH 23/28] normalize routing and dns --- rdap/schema/normalized.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 9d11c0f..3929b84 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -59,6 +59,8 @@ class ROLE(str, enum.Enum): "administrative": "admin", "noc": "technical", "registrar": "registrant", + "routing": "technical", + "dns": "technical", } From aff5dbfa59f49b4ea02513b36937967ba3180023 Mon Sep 17 00:00:00 2001 From: Stefan Pratter Date: Thu, 1 Aug 2024 17:09:49 +0000 Subject: [PATCH 24/28] handle undefined roles --- rdap/schema/normalized.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 3929b84..5ee6575 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -53,6 +53,8 @@ class ROLE(str, enum.Enum): policy = "policy" technical = "technical" registrant = "registrant" + billing = "billing" + sponsor = "sponsor" NORMALIZED_ROLES = { @@ -64,6 +66,9 @@ class ROLE(str, enum.Enum): } +VALID_ROLES = [str(r) for r in ROLE] + + class DNSSEC(str, enum.Enum): secure = "secure" insecure = "insecure" @@ -122,9 +127,11 @@ def normalize_roles(cls, data: Any) -> Any: data["roles"] = roles # drop duplicates - data["roles"] = list(set(data["roles"])) + # drop any invalid roles + data["roles"] = [role for role in data["roles"] if f"ROLE.{role}" in VALID_ROLES] + return data def __hash__(self): From a5453ec3e5723597edb831e5da20d89f9e48684a Mon Sep 17 00:00:00 2001 From: Matt Griswold Date: Sat, 7 Sep 2024 20:19:00 +0000 Subject: [PATCH 25/28] prep v1.6 --- CHANGELOG.md | 5 + CHANGELOG.yaml | 3 + Ctl/config.yml | 2 +- poetry.lock | 785 ++++++++++++++++++++++++------------------------- 4 files changed, 390 insertions(+), 405 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12029a2..3f21b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## Unreleased +## 1.6.0 +### Added +- add regctl normalization schema + + ## 1.5.2 ### Fixed - issue introduced in 1.5.1 that could cause some contact points to be lost diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index b49935b..875cf03 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -5,6 +5,9 @@ Unreleased: deprecated: [] removed: [] security: [] +1.6.0: + added: + - add regctl normalization schema 1.5.2: fixed: - issue introduced in 1.5.1 that could cause some contact points to be lost diff --git a/Ctl/config.yml b/Ctl/config.yml index 7166655..32f15cd 100644 --- a/Ctl/config.yml +++ b/Ctl/config.yml @@ -20,7 +20,7 @@ ctl: sign: true - name: version - type: version + type: semver2 config: branch_dev: main branch_release: main diff --git a/poetry.lock b/poetry.lock index 7661bc5..4b2d2bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -17,34 +16,33 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "black" -version = "24.4.2" +version = "24.8.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, ] [package.dependencies] @@ -64,33 +62,30 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cachetools" -version = "5.4.0" +version = "5.5.0" description = "Extensible memoizing collections and decorators" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -102,7 +97,6 @@ files = [ name = "chardet" version = "5.2.0" description = "Universal encoding detector for Python 3" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -114,7 +108,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -214,7 +207,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -229,7 +221,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -239,64 +230,83 @@ files = [ [[package]] name = "coverage" -version = "7.6.0" +version = "7.6.1" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -309,7 +319,6 @@ toml = ["tomli"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -321,7 +330,6 @@ files = [ name = "distlib" version = "0.3.8" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -333,7 +341,6 @@ files = [ name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -346,26 +353,24 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.15.4" +version = "3.16.0" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, + {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -382,7 +387,6 @@ pyflakes = ">=2.5.0,<2.6.0" name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." -category = "dev" optional = false python-versions = "*" files = [ @@ -400,7 +404,6 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "googlemaps" version = "4.10.0" description = "Python client library for Google Maps Platform" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -414,7 +417,6 @@ requests = ">=2.20.0,<3.0" name = "identify" version = "2.6.0" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -427,26 +429,24 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] name = "importlib-metadata" -version = "8.0.0" +version = "8.4.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, - {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, ] [package.dependencies] @@ -461,7 +461,6 @@ test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "p name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -473,7 +472,6 @@ files = [ name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -488,7 +486,6 @@ colors = ["colorama (>=0.4.6)"] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -504,14 +501,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.6" +version = "3.7" description = "Python implementation of John Gruber's Markdown." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, - {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] [package.dependencies] @@ -525,7 +521,6 @@ testing = ["coverage", "pyyaml"] name = "markdown-include" version = "0.8.1" description = "A Python-Markdown extension which provides an 'include' function" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -543,7 +538,6 @@ tests = ["pytest"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -613,7 +607,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -625,7 +618,6 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -635,14 +627,13 @@ files = [ [[package]] name = "mkdocs" -version = "1.6.0" +version = "1.6.1" description = "Project documentation with Markdown." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, - {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, ] [package.dependencies] @@ -669,7 +660,6 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp name = "mkdocs-get-deps" version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -687,7 +677,6 @@ pyyaml = ">=5.1" name = "munge" version = "1.3.0" description = "data manipulation library and client" -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -707,45 +696,44 @@ yaml = ["PyYAML (>=5.1)"] [[package]] name = "mypy" -version = "1.10.1" +version = "1.11.2" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, - {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, - {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, - {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, - {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, - {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, - {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, - {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, - {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, - {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, - {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, - {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, - {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, - {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, - {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, - {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, - {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, - {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, - {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -757,7 +745,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -769,7 +756,6 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -781,7 +767,6 @@ files = [ name = "packaging" version = "24.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -793,7 +778,6 @@ files = [ name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -803,26 +787,24 @@ files = [ [[package]] name = "phonenumbers" -version = "8.13.40" +version = "8.13.45" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.40-py2.py3-none-any.whl", hash = "sha256:9582752c20a1da5ec4449f7f97542bf8a793c8e2fec0ab57f767177bb8fc0b1d"}, - {file = "phonenumbers-8.13.40.tar.gz", hash = "sha256:f137c2848b8e83dd064b71881b65680584417efa202177fd330e2f7ff6c68113"}, + {file = "phonenumbers-8.13.45-py2.py3-none-any.whl", hash = "sha256:bf05ec20fcd13f0d53e43a34ed7bd1c8be26a72b88fce4b8c64fca5b4641987a"}, + {file = "phonenumbers-8.13.45.tar.gz", hash = "sha256:53679a95b6060fd5e15467759252c87933d8566d6a5be00995a579eb0e02435b"}, ] [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.1-py3-none-any.whl", hash = "sha256:facaa5a3c57aa1e053e3da7b49e0cc31fe0113ca42a4659d5c2e98e545624afe"}, + {file = "platformdirs-4.3.1.tar.gz", hash = "sha256:63b79589009fa8159973601dd4563143396b35c5f93a58b36f9049ff046949b1"}, ] [package.extras] @@ -834,7 +816,6 @@ type = ["mypy (>=1.8)"] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -850,7 +831,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -869,7 +849,6 @@ virtualenv = ">=20.10.0" name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -879,124 +858,123 @@ files = [ [[package]] name = "pydantic" -version = "2.8.2" +version = "2.9.0" description = "Data validation using Python type hints" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, + {file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"}, + {file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" +pydantic-core = "2.23.2" typing-extensions = [ {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, ] +tzdata = {version = "*", markers = "python_version >= \"3.9\""} [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.23.2" description = "Core functionality for Pydantic validation and serialization" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, + {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, + {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, + {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, + {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, + {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, + {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, + {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, + {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, + {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, + {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, + {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, + {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, + {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, ] [package.dependencies] @@ -1006,7 +984,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1018,7 +995,6 @@ files = [ name = "pyproject-api" version = "1.7.1" description = "API to interact with the python pyproject.toml based projects" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1036,14 +1012,13 @@ testing = ["covdefaults (>=2.3)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytes [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -1051,7 +1026,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -1061,7 +1036,6 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1078,14 +1052,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" -version = "4.8.0" +version = "4.9.0" description = "A Django plugin for pytest." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, - {file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, + {file = "pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99"}, + {file = "pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314"}, ] [package.dependencies] @@ -1099,7 +1072,6 @@ testing = ["Django", "django-configurations (>=2.0)"] name = "pytest-filedata" version = "1.0.0" description = "easily load test data from files" -category = "dev" optional = false python-versions = "<4.0,>=3.8" files = [ @@ -1115,7 +1087,6 @@ requests-mock = ">=1" name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1130,7 +1101,6 @@ six = ">=1.5" name = "pyupgrade" version = "3.8.0" description = "A tool to automatically upgrade syntax for newer versions." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1143,69 +1113,70 @@ tokenize-rt = ">=3.2.0" [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1220,7 +1191,6 @@ pyyaml = "*" name = "requests" version = "2.32.3" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1242,7 +1212,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.12.1" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1260,7 +1229,6 @@ fixture = ["fixtures"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1270,21 +1238,19 @@ files = [ [[package]] name = "tokenize-rt" -version = "5.2.0" +version = "6.0.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"}, - {file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"}, + {file = "tokenize_rt-6.0.0-py2.py3-none-any.whl", hash = "sha256:d4ff7ded2873512938b4f8cbb98c9b07118f01d30ac585a30d7a88353ca36d22"}, + {file = "tokenize_rt-6.0.0.tar.gz", hash = "sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1294,18 +1260,17 @@ files = [ [[package]] name = "tox" -version = "4.16.0" +version = "4.18.1" description = "tox is a generic virtualenv management and test command line tool" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.16.0-py3-none-any.whl", hash = "sha256:61e101061b977b46cf00093d4319438055290ad0009f84497a07bf2d2d7a06d0"}, - {file = "tox-4.16.0.tar.gz", hash = "sha256:43499656f9949edb681c0f907f86fbfee98677af9919d8b11ae5ad77cb800748"}, + {file = "tox-4.18.1-py3-none-any.whl", hash = "sha256:35d472032ee1f73fe20c3e0e73d7073a4e85075c86ff02c576f9fc7c6a15a578"}, + {file = "tox-4.18.1.tar.gz", hash = "sha256:3c0c96bc3a568a5c7e66387a4cfcf8c875b52e09f4d47c9f7a277ec82f1a0b11"}, ] [package.dependencies] -cachetools = ">=5.3.3" +cachetools = ">=5.5" chardet = ">=5.2" colorama = ">=0.4.6" filelock = ">=3.15.4" @@ -1317,14 +1282,13 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} virtualenv = ">=20.26.3" [package.extras] -docs = ["furo (>=2024.5.6)", "sphinx (>=7.3.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] -testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.2)", "time-machine (>=2.14.2)", "wheel (>=0.43)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-argparse-cli (>=1.17)", "sphinx-autodoc-typehints (>=2.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=24.8)"] +testing = ["build[virtualenv] (>=1.2.2)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=74.1.2)", "time-machine (>=2.15)", "wheel (>=0.44)"] [[package]] name = "tox-gh-actions" version = "3.2.0" description = "Seamless integration of tox into GitHub Actions" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1342,7 +1306,6 @@ testing = ["black", "devpi-process", "flake8 (>=6,<7)", "mypy", "pytest (>=7,<8) name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1350,11 +1313,21 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "urllib3" version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1370,14 +1343,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.26.4" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, + {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, ] [package.dependencies] @@ -1391,44 +1363,46 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "4.0.1" +version = "4.0.2" description = "Filesystem events monitoring" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, - {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, - {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, - {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, - {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, + {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, + {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, + {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, + {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, ] [package.extras] @@ -1436,19 +1410,22 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.1" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, + {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.0" From fb91539804d6a35f46a6a8ca7bc0870eef1ebf14 Mon Sep 17 00:00:00 2001 From: Matt Griswold Date: Sat, 7 Sep 2024 20:20:22 +0000 Subject: [PATCH 26/28] Version 1.6.0 --- Ctl/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ctl/VERSION b/Ctl/VERSION index a73b432..ce6a70b 100644 --- a/Ctl/VERSION +++ b/Ctl/VERSION @@ -1 +1 @@ -1.5.2 \ No newline at end of file +1.6.0 \ No newline at end of file From 08abd06606a394a9ac957a20071092d0e9cefe97 Mon Sep 17 00:00:00 2001 From: Matt Griswold Date: Sat, 7 Sep 2024 20:26:14 +0000 Subject: [PATCH 27/28] lint --- docs/api.md | 2 +- rdap/normalize/base.py | 4 +++- rdap/schema/normalized.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/api.md b/docs/api.md index dbbaee5..03868c7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -11,7 +11,7 @@ client = rdap.RdapClient() # asn as63311 = client.get_asn(63311) -# domain +# domain domain = client.get_domain('example.com') # ip diff --git a/rdap/normalize/base.py b/rdap/normalize/base.py index 2e8625e..c5b35e9 100644 --- a/rdap/normalize/base.py +++ b/rdap/normalize/base.py @@ -209,7 +209,9 @@ def recurse_contacts( contacts.extend( [ Contact(**contact) - for contact in ctx.get("entity", entity.handle).get("contacts", []) + for contact in ctx.get("entity", entity.handle).get( + "contacts", [] + ) ] ) diff --git a/rdap/schema/normalized.py b/rdap/schema/normalized.py index 5ee6575..7c81de0 100644 --- a/rdap/schema/normalized.py +++ b/rdap/schema/normalized.py @@ -130,7 +130,9 @@ def normalize_roles(cls, data: Any) -> Any: data["roles"] = list(set(data["roles"])) # drop any invalid roles - data["roles"] = [role for role in data["roles"] if f"ROLE.{role}" in VALID_ROLES] + data["roles"] = [ + role for role in data["roles"] if f"ROLE.{role}" in VALID_ROLES + ] return data From e890a5480d68a54836600f487c8f20b8dd000db1 Mon Sep 17 00:00:00 2001 From: Matt Griswold Date: Sat, 28 Sep 2024 16:35:27 -0500 Subject: [PATCH 28/28] poerty lock --- poetry.lock | 300 ++++++++++++++++++++++++++-------------------------- 1 file changed, 148 insertions(+), 152 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4b2d2bb..7633dec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -353,18 +353,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.16.0" +version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, - {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] @@ -415,13 +415,13 @@ requests = ">=2.20.0,<3.0" [[package]] name = "identify" -version = "2.6.0" +version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, + {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, + {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, ] [package.extras] @@ -429,33 +429,40 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.8" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ - {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, - {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "importlib-metadata" -version = "8.4.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -787,30 +794,30 @@ files = [ [[package]] name = "phonenumbers" -version = "8.13.45" +version = "8.13.46" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.45-py2.py3-none-any.whl", hash = "sha256:bf05ec20fcd13f0d53e43a34ed7bd1c8be26a72b88fce4b8c64fca5b4641987a"}, - {file = "phonenumbers-8.13.45.tar.gz", hash = "sha256:53679a95b6060fd5e15467759252c87933d8566d6a5be00995a579eb0e02435b"}, + {file = "phonenumbers-8.13.46-py2.py3-none-any.whl", hash = "sha256:519422d407af066fdbf98e179ea2e214487060f26526d67871f817eefbbb2134"}, + {file = "phonenumbers-8.13.46.tar.gz", hash = "sha256:94bf18ba9725bb6868d29473b13f78ef01e2585c5cb561ec0200be7676e77452"}, ] [[package]] name = "platformdirs" -version = "4.3.1" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.3.1-py3-none-any.whl", hash = "sha256:facaa5a3c57aa1e053e3da7b49e0cc31fe0113ca42a4659d5c2e98e545624afe"}, - {file = "platformdirs-4.3.1.tar.gz", hash = "sha256:63b79589009fa8159973601dd4563143396b35c5f93a58b36f9049ff046949b1"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" @@ -858,123 +865,123 @@ files = [ [[package]] name = "pydantic" -version = "2.9.0" +version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"}, - {file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"}, + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.23.2" +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" typing-extensions = [ {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, ] -tzdata = {version = "*", markers = "python_version >= \"3.9\""} [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.2" +version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, - {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, - {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, - {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, - {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, - {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, - {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, - {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, - {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, - {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, - {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, - {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, - {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, - {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, - {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, - {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, - {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, - {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, - {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, - {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, - {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, - {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, - {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, - {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, - {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, - {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, - {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, - {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, - {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, - {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, - {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, - {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, - {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, - {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] @@ -993,13 +1000,13 @@ files = [ [[package]] name = "pyproject-api" -version = "1.7.1" +version = "1.8.0" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.8" files = [ - {file = "pyproject_api-1.7.1-py3-none-any.whl", hash = "sha256:2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb"}, - {file = "pyproject_api-1.7.1.tar.gz", hash = "sha256:7ebc6cd10710f89f4cf2a2731710a98abce37ebff19427116ff2174c9236a827"}, + {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, + {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, ] [package.dependencies] @@ -1007,18 +1014,18 @@ packaging = ">=24.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [package.extras] -docs = ["furo (>=2024.5.6)", "sphinx-autodoc-typehints (>=2.2.1)"] -testing = ["covdefaults (>=2.3)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=70.1)"] +docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -1260,13 +1267,13 @@ files = [ [[package]] name = "tox" -version = "4.18.1" +version = "4.20.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.18.1-py3-none-any.whl", hash = "sha256:35d472032ee1f73fe20c3e0e73d7073a4e85075c86ff02c576f9fc7c6a15a578"}, - {file = "tox-4.18.1.tar.gz", hash = "sha256:3c0c96bc3a568a5c7e66387a4cfcf8c875b52e09f4d47c9f7a277ec82f1a0b11"}, + {file = "tox-4.20.0-py3-none-any.whl", hash = "sha256:21a8005e3d3fe5658a8e36b8ca3ed13a4230429063c5cc2a2fdac6ee5aa0de34"}, + {file = "tox-4.20.0.tar.gz", hash = "sha256:5b78a49b6eaaeab3ae4186415e7c97d524f762ae967c63562687c3e5f0ec23d5"}, ] [package.dependencies] @@ -1313,26 +1320,15 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -1343,13 +1339,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.4" +version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, - {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, + {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, + {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, ] [package.dependencies] @@ -1410,13 +1406,13 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "zipp" -version = "3.20.1" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, - {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras]