From 8d495cd8b78fe8545db2ff633399cd86171b615c Mon Sep 17 00:00:00 2001 From: Jordan Gillard Date: Thu, 24 Oct 2024 07:36:18 -0400 Subject: [PATCH] Create new URL encoding function to align with SEC API (#40) * Update README * examples w/ x-platform escape chars *exact search instructions * feat: Add support for single forms * feat: Add search param validator and basic tests * test: Add invalid arg tests * test: Add invalid custom date tests * test: Raises error for invalid date_range_select * test: Add tests for all date options * test: Add test for invalid peo_in * test: Add tests for incorportated in * Add filing categories to url_generator * Remove duplicate filing category to form id code * Amend me! Add TODO for remaining work * Add ISO 3166-1/2 country codes to EDGAR codes * Add urls for remaining countries * Update url_generator to use EDGAR API URL * Use urllib.parse.quote to encode quote characters * Avoid double escaping keywords in url and handle single quotes --------- Co-authored-by: Galen Reich <54807169+GalenReich@users.noreply.github.com> --- README.md | 41 +- edgar_tool/{main.py => __main__.py} | 4 +- edgar_tool/constants.py | 330 +++++++++++++++- edgar_tool/text_search.py | 8 +- edgar_tool/url_generator.py | 160 ++++++++ poetry.lock | 306 ++++++++------- pyproject.toml | 3 +- tests/test_url_generator.py | 584 ++++++++++++++++++++++++++++ 8 files changed, 1264 insertions(+), 172 deletions(-) rename edgar_tool/{main.py => __main__.py} (73%) create mode 100644 edgar_tool/url_generator.py create mode 100644 tests/test_url_generator.py diff --git a/README.md b/README.md index 33e96c9..55ab32f 100644 --- a/README.md +++ b/README.md @@ -38,26 +38,49 @@ This is a command line tool that takes a search query, queries a server, and dow ### Examples -```bash -# Display help message describing all supported arguments along with their usage, aliases and eventual default values (type q to exit) +Display help message describing all supported arguments along with their usage, aliases and eventual default values (type `q` to exit) + +```shell edgar-tool text_search --help +``` + +Basic usage (defaults to searching the last 5 years of records) -# Basic usage (defaults to searching the last 5 years of records) +```shell edgar-tool text_search John Doe +``` + +You can wrap a phrase in quotes if you want an exact match. +This works in both POSIX-compliant shells (Linux/Bash) and Windows PowerShell environments. -# Basic usage with a combination of exact and partial search parameters -edgar-tool text_search \"John Doe\" Pharmaceuticals Chemicals +For example, the following usage will search for the exact phrase `"John Doe"` and treat `Pharmaceuticals` and +`Chemicals` as partial search parameters. + +```shell +edgar-tool text_search "John Doe" Pharmaceuticals Chemicals +``` -# Usage with date range and export to custom CSV file +Usage with date range and export to custom CSV file + +```shell edgar-tool text_search Tsunami Hazards --start_date "2021-01-01" --end_date "2021-12-31" --output "results.csv" +``` -# Usage with a partial set of filing forms + single forms +### Usage with a partial set of filing forms + single forms + +``` edgar-tool text_search Hurricane Damage --filing_form "registration_statements" --single_forms "['1-K', '1-SA']" +``` + +Usage specifying the location of incorporation -# Usage specifying the location of incorporation +```shell edgar-tool text_search oil --inc_in "Egypt" +``` + +More advanced usage specifying more arguments, with export to JSON -# More advanced usage specifying more arguments, with export to JSON +```shell edgar-tool text_search Volcano Monitoring --start_date "2021-01-01" --end_date "2021-12-31" --output "results.json"\ --filing_form "all_annual_quarterly_and_current_reports" --entity_id "0001030717" \ --min_wait 5.0 --max_wait 7.0 --retries 3 diff --git a/edgar_tool/main.py b/edgar_tool/__main__.py similarity index 73% rename from edgar_tool/main.py rename to edgar_tool/__main__.py index 1b59768..03252a9 100644 --- a/edgar_tool/main.py +++ b/edgar_tool/__main__.py @@ -3,9 +3,9 @@ from edgar_tool.cli import SecEdgarScraperCli -def main_entrypoint(): +def main(): fire.Fire(SecEdgarScraperCli) if __name__ == "__main__": - main_entrypoint() + main() diff --git a/edgar_tool/constants.py b/edgar_tool/constants.py index 9b79752..f378c7b 100644 --- a/edgar_tool/constants.py +++ b/edgar_tool/constants.py @@ -1,18 +1,5 @@ SUPPORTED_OUTPUT_EXTENSIONS = [".csv", ".jsonl", ".json"] TEXT_SEARCH_BASE_URL = "https://efts.sec.gov/LATEST/search-index?" -TEXT_SEARCH_FILING_CATEGORIES_MAPPING = { - "all_except_section_16": "form-cat0", - "all_annual_quarterly_and_current_reports": "form-cat1", - "all_section_16": "form-cat2", - "beneficial_ownership_reports": "form-cat3", - "exempt_offerings": "form-cat4", - "registration_statements": "form-cat5", - "filing_review_correspondence": "form-cat6", - "sec_orders_and_notices": "form-cat7", - "proxy_materials": "form-cat8", - "tender_offers_and_going_private_tx": "form-cat9", - "trust_indentures": "form-cat10", -} TEXT_SEARCH_SPLIT_BATCHES_NUMBER = 2 TEXT_SEARCH_CSV_FIELDS_NAMES = [ "root_form", @@ -52,6 +39,323 @@ "xbrl_files", ] +"""All mappings below are from the SEC EDGAR website's search form. +The keys are the values that the CLI uses, and the values are those +that the search form uses. All values are shown in the order they +appear in the SEC EDGAR search drop down.""" +PEO_IN_AND_INC_IN_TO_SEC_FORM_ID = { + # US States + "AL": "AL", + "AK": "AK", + "AZ": "AZ", + "AR": "AR", + "CA": "CA", + "CO": "CO", + "CT": "CT", + "DE": "DE", + "DC": "DC", + "FL": "FL", + "GA": "GA", + "HI": "HI", + "ID": "ID", + "IL": "IL", + "IN": "IN", + "IA": "IA", + "KS": "KS", + "KY": "KY", + "LA": "LA", + "ME": "ME", + "MD": "MD", + "MA": "MA", + "MI": "MI", + "MN": "MN", + "MS": "MS", + "MO": "MO", + "MT": "MT", + "NE": "NE", + "NV": "NV", + "NH": "NH", + "NJ": "NJ", + "NM": "NM", + "NY": "NY", + "NC": "NC", + "ND": "ND", + "OH": "OH", + "OK": "OK", + "OR": "OR", + "PA": "PA", + "RI": "RI", + "SC": "SC", + "SD": "SD", + "TN": "TN", + "TX": "TX", + "UT": "UT", + "VT": "VT", + "VA": "VA", + "WA": "WA", + "WV": "WV", + "WI": "WI", + "WY": "WY", + # Canadian Provinces + "AB": "A0", + "BC": "A1", + "CAN": "Z4", # Canada (Federal Level) + "MB": "A2", + "NB": "A3", + "NL": "A4", + "NS": "A5", + "ON": "A6", + "PE": "A7", + "QC": "A8", + "SK": "A9", + "YT": "B0", + # Countries + "AFG": "B2", + "ALA": "Y6", + "ALB": "B3", + "DZA": "B4", + "ASM": "B5", + "AND": "B6", + "AGO": "B7", + "AIA": "1A", + "ATA": "B8", + "ATG": "B9", + "ARG": "C1", + "ARM": "1B", + "ABW": "1C", + "AUS": "C3", + "AUT": "C4", + "AZE": "1D", + "BHS": "C5", + "BHR": "C6", + "BGD": "C7", + "BRB": "C8", + "BLR": "1F", + "BEL": "C9", + "BLZ": "D1", + "BEN": "G6", + "BMU": "D0", + "BTN": "D2", + "BOL": "D3", + "BIH": "1E", + "BWA": "B1", + "BVT": "D4", + "BRA": "D5", + "IOT": "D6", + "BRN": "D9", + "BGR": "E0", + "BFA": "X2", + "BDI": "E2", + "KHM": "E3", + "CMR": "E4", + "CPV": "E8", + "CYM": "E9", + "CAF": "F0", + "TCD": "F2", + "CHL": "F3", + "CHN": "F4", + "CXR": "F6", + "CCK": "F7", + "COL": "F8", + "COM": "F9", + "COG": "G0", + "COD": "Y3", + "COK": "G1", + "CRI": "G2", + "CIV": "L7", + "HRV": "1M", + "CUB": "G3", + "CYP": "G4", + "CZE": "2N", + "DNK": "G7", + "DJI": "1G", + "DMA": "G9", + "DOM": "D8", + "ECU": "H1", + "EGY": "H2", + "SLV": "H3", + "GNQ": "H4", + "ERI": "1J", + "EST": "1H", + "ETH": "H5", + "FLK": "H7", + "FRO": "H6", + "FJI": "H8", + "FIN": "H9", + "FRA": "I0", + "GUF": "I3", + "PYF": "I4", + "ATF": "2C", + "GAB": "I5", + "GMB": "I6", + "GEO": "2Q", + "DEU": "2M", + "GHA": "J0", + "GIB": "J1", + "GRC": "J3", + "GRL": "J4", + "GRD": "J5", + "GLP": "J6", + "GUM": "GU", + "GTM": "J8", + "GGY": "Y7", + "GIN": "J9", + "GNB": "S0", + "GUY": "K0", + "HTI": "K1", + "HMD": "K4", + "VAT": "X4", + "HND": "K2", + "HKG": "K3", + "HUN": "K5", + "ISL": "K6", + "IND": "K7", + "IDN": "K8", + "IRN": "K9", + "IRQ": "L0", + "IRL": "L2", + "IMN": "Y8", + "ISR": "L3", + "ITA": "L6", + "JAM": "L8", + "JPN": "M0", + "JEY": "Y9", + "JOR": "M2", + "KAZ": "1P", + "KEN": "M3", + "KIR": "J2", + "PRK": "M4", + "KOR": "M5", + "KWT": "M6", + "KGZ": "1N", + "LAO": "M7", + "LVA": "1R", + "LBN": "M8", + "LSO": "M9", + "LBR": "N0", + "LBY": "N1", + "LIE": "N2", + "LTU": "1Q", + "LUX": "N4", + "MAC": "N5", + "MKD": "1U", + "MDG": "N6", + "MWI": "N7", + "MYS": "N8", + "MDV": "N9", + "MLI": "O0", + "MLT": "O1", + "MHL": "1T", + "MTQ": "O2", + "MRT": "O3", + "MUS": "O4", + "MYT": "2P", + "MEX": "O5", + "FSM": "1K", + "MDA": "1S", + "MCO": "O9", + "MNG": "P0", + "MNE": "Z5", + "MSR": "P1", + "MAR": "P2", + "MOZ": "P3", + "MMR": "E1", + "NAM": "T6", + "NRU": "P5", + "NPL": "P6", + "NLD": "P7", + "ANT": "P8", + "NCL": "1W", + "NZL": "Q2", + "NIC": "Q3", + "NER": "Q4", + "NGA": "Q5", + "NIU": "Q6", + "NFK": "Q7", + "MNP": "1V", + "NOR": "Q8", + "OMN": "P4", + "PAK": "R0", + "PLW": "1Y", + "PSE": "1X", + "PAN": "R1", + "PNG": "R2", + "PRY": "R4", + "PER": "R5", + "PHL": "R6", + "PCN": "R8", + "POL": "R9", + "PRT": "S1", + "PRI": "PR", + "QAT": "S3", + "REU": "S4", + "ROU": "S5", + "RUS": "1Z", + "RWA": "S6", + "BLM": "Z0", + "SHN": "U8", + "KNA": "U7", + "LCA": "U9", + "MAF": "Z1", + "SPM": "V0", + "VCT": "V1", + "WSM": "Y0", + "SMR": "S8", + "STP": "S9", + "SAU": "T0", + "SEN": "T1", + "SRB": "Z2", + "SYC": "T2", + "SLE": "T8", + "SGP": "U0", + "SVK": "2B", + "SVN": "2A", + "SLB": "D7", + "SOM": "U1", + "ZAF": "T3", + "SGS": "1L", + "ESP": "U3", + "LKA": "F1", + "SDN": "V2", + "SUR": "V3", + "SJM": "L9", + "SWZ": "V6", + "SWE": "V7", + "CHE": "V8", + "SYR": "V9", + "TWN": "F5", + "TJK": "2D", + "THA": "W1", + "TLS": "Z3", + "TGO": "W2", + "TKL": "W3", + "TON": "W4", + "TTO": "W5", + "TUN": "W6", + "TUR": "W8", + "TKM": "2E", + "TCA": "W7", + "TUV": "2G", + "UGA": "W9", + "UKR": "2H", + "ARE": "C0", + "GBR": "X0", + "UMI": "2J", + "URY": "X3", + "UZB": "2K", + "VUT": "2L", + "VEN": "X5", + "VNM": "Q1", + "VGB": "D8", + "VIR": "VI", + "WLF": "X8", + "ESH": "Y1", + "YEM": "T7", + "ZMB": "Y4", + "ZWE": "Y5", + "XX": "XX", +} + TEXT_SEARCH_LOCATIONS_MAPPING = { "AL": "Alabama", "AK": "Alaska", diff --git a/edgar_tool/text_search.py b/edgar_tool/text_search.py index 018438f..a8875ba 100644 --- a/edgar_tool/text_search.py +++ b/edgar_tool/text_search.py @@ -16,10 +16,10 @@ ) from edgar_tool.io import write_results_to_file from edgar_tool.page_fetcher import ( - NoResultsFoundError, + fetch_page, PageCheckFailedError, ResultsTableNotFoundError, - fetch_page, + NoResultsFoundError, ) from edgar_tool.utils import split_date_range_in_n, unpack_singleton_list @@ -217,7 +217,9 @@ def _generate_request_args( raise ValueError("start_date cannot be after end_date") # Join search keywords into a single string - keywords = " ".join(keywords) + keywords = " ".join( + [f'"{keyword}"' if " " in keyword else keyword for keyword in keywords] + ) # Generate request arguments request_args = { diff --git a/edgar_tool/url_generator.py b/edgar_tool/url_generator.py new file mode 100644 index 0000000..1b47f46 --- /dev/null +++ b/edgar_tool/url_generator.py @@ -0,0 +1,160 @@ +import datetime +from typing import Literal, TypedDict +from urllib import parse + +from edgar_tool.constants import PEO_IN_AND_INC_IN_TO_SEC_FORM_ID, TEXT_SEARCH_BASE_URL + + +class SearchQueryKwargs(TypedDict, total=False): + keywords: list[str] + entity: str + filing_category: str + single_forms: list[str] + date_range_select: Literal["all", "10y", "1y", "30d", "custom"] + start_date: datetime.date + end_date: datetime.date + inc_in: str + peo_in: str + + +class _ValidSearchParams: + def __init__(self, **query_args: SearchQueryKwargs): + keywords = query_args.get("keywords") + entity = query_args.get("entity") + if not keywords and not entity: + raise ValueError( + "Invalid search arguments. You must provide keywords or an entity." + ) + + date_range_select = query_args.get("date_range_select") + start_date = query_args.get("start_date") + end_date = query_args.get("end_date") + if date_range_select == "custom" and not (start_date and end_date): + raise ValueError( + ( + "Invalid date parameters. " + "You must provide both a start and end date if searching a custom date range." + ) + ) + elif date_range_select and date_range_select not in { + "all", + "10y", + "1y", + "30d", + "custom", + }: + raise ValueError( + ( + "Invalid date_range_select. " + 'Value must be one of "all", "10y", "1y", "30d", or "custom"' + ) + ) + + self._keywords = keywords + self.entity = entity + + filing_category = query_args.get("filing_category", "custom") + single_forms = query_args.get("single_forms") + if filing_category != "custom" and single_forms: + raise ValueError( + "Cannot specify both filing_category and single_forms. " + "Passing single_forms automatically sets the filing_category" + " to custom. Please choose one or the other." + ) + + self._filing_category = filing_category + self.single_forms = single_forms + self.date_range_select = date_range_select + self.start_date = start_date + self.end_date = end_date + + peo_in = query_args.get("peo_in") + inc_in = query_args.get("inc_in") + if peo_in and inc_in: + raise ValueError( + "Cannot specify both peo_in and inc_in. Please choose one or the other." + ) + if ( + peo_in + and peo_in not in PEO_IN_AND_INC_IN_TO_SEC_FORM_ID + or inc_in + and inc_in not in PEO_IN_AND_INC_IN_TO_SEC_FORM_ID + ): + raise ValueError( + ( + "Invalid location code. " + "Please provide a valid 2-letter state abbreviation, " + "3-letter country code, or 'XX' for unknown." + ) + ) + self.inc_in = inc_in + self.peo_in = peo_in + + @property + def keywords(self): + return self._keywords + + @keywords.getter + def keywords(self): + """Returns the keywords to search for, wrapping exact phrases in quotes.""" + return [f'"{phrase}"' if " " in phrase else phrase for phrase in self._keywords] + + @property + def filing_category(self): + return self._filing_category + + @keywords.getter + def filing_category(self): + filing_category_to_sec_form_id = { + "all": "", + "all_except_section_16": "form-cat0", + "all_annual_quarterly_and_current_reports": "form-cat1", + "all_section_16": "form-cat2", + "beneficial_ownership_reports": "form-cat3", + "exempt_offerings": "form-cat4", + "registration_statements": "form-cat5", + "filing_review_correspondence": "form-cat6", + "sec_orders_and_notices": "form-cat7", + "proxy_materials": "form-cat8", + "tender_offers_and_going_private_tx": "form-cat9", + "trust_indentures": "form-cat10", + } + return filing_category_to_sec_form_id.get(self._filing_category) + + +def generate_search_url_for_kwargs(search_kwargs: SearchQueryKwargs) -> str: + validated_params = _ValidSearchParams(**search_kwargs) + query_params = { + "q": validated_params.keywords, + } + if date_range_select := validated_params.date_range_select: + query_params.update( + { + "dateRange": date_range_select, + } + ) + if date_range_select == "custom": + query_params.update( + { + "startdt": validated_params.start_date.strftime("%Y-%m-%d"), + "enddt": validated_params.end_date.strftime("%Y-%m-%d"), + } + ) + if validated_params.filing_category: + query_params["category"] = validated_params.filing_category + elif validated_params.single_forms: + query_params["category"] = "custom" + query_params["forms"] = validated_params.single_forms + if validated_params.peo_in: + query_params["locationCode"] = PEO_IN_AND_INC_IN_TO_SEC_FORM_ID[ + validated_params.peo_in + ] + elif validated_params.inc_in: + query_params["locationType"] = "incorporated" + query_params["locationCode"] = PEO_IN_AND_INC_IN_TO_SEC_FORM_ID[ + validated_params.inc_in + ] + encoded_params = parse.urlencode( + query_params, doseq=True, encoding="utf-8", quote_via=parse.quote + ) + return TEXT_SEARCH_BASE_URL + encoded_params diff --git a/poetry.lock b/poetry.lock index 6a2d972..e0b0526 100644 --- a/poetry.lock +++ b/poetry.lock @@ -111,101 +111,116 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -235,13 +250,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] @@ -260,18 +275,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]] @@ -290,13 +305,13 @@ termcolor = "*" [[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] @@ -304,15 +319,18 @@ 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 = "iniconfig" version = "2.0.0" @@ -398,19 +416,19 @@ files = [ [[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" @@ -447,13 +465,13 @@ virtualenv = ">=20.10.0" [[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] @@ -461,18 +479,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] @@ -597,13 +615,13 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "termcolor" -version = "2.4.0" +version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, ] [package.extras] @@ -611,41 +629,41 @@ tests = ["pytest", "pytest-cov"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "tox" -version = "4.18.1" +version = "4.23.2" 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.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"}, + {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"}, ] [package.dependencies] cachetools = ">=5.5" chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.15.4" +filelock = ">=3.16.1" packaging = ">=24.1" -platformdirs = ">=4.2.2" +platformdirs = ">=4.3.6" pluggy = ">=1.5" -pyproject-api = ">=1.7.1" +pyproject-api = ">=1.8" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} -virtualenv = ">=20.26.3" +typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} +virtualenv = ">=20.26.6" [package.extras] -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)"] +test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"] [[package]] name = "typing-extensions" @@ -660,13 +678,13 @@ files = [ [[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] @@ -677,13 +695,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.4" +version = "20.27.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" 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.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, + {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, ] [package.dependencies] @@ -709,4 +727,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "cebc53eb0c3700be24a100b720c492b937227b4bdddf6be41bce417f317582b4" +content-hash = "d2ba7e1c112d61ff3449daf5f60a354669c9903e74e13e1240a557103cb208f2" diff --git a/pyproject.toml b/pyproject.toml index 15a37ae..41305d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ keywords=["scraper", "edgar", "finance", "sec"] "Bug Tracker" = "https://github.com/bellingcat/EDGAR/issues" [tool.poetry.scripts] -edgar-tool = "edgar_tool.main:main_entrypoint" +edgar-tool = "edgar_tool.__main__:main" [tool.poetry.dependencies] fire = "^0.5" @@ -33,6 +33,7 @@ python = "^3.9" requests = "^2.31" tenacity = "^8.2" xmltodict = "^0.13" +pytest = "^8.2.2" [tool.poetry.group.dev.dependencies] black = "24.3.0" diff --git a/tests/test_url_generator.py b/tests/test_url_generator.py new file mode 100644 index 0000000..27efb9f --- /dev/null +++ b/tests/test_url_generator.py @@ -0,0 +1,584 @@ +""" +This file tests code responsible for generating the EDGAR search URL. + +Note that at the time of this writing, EDGAR double-encodes query search +parameters so that %-encoded characters, like a quote ("), are encoded +as %2520 in the browser's URL instead of %20. This is a quirk with the +SEC's search functionality. Local testing indicates that single-encoded +URLs (which is the norm) and double-encoded URLs produce the same +responses. + +I.e. this double-encoded URL produced on the SEC's EDGAR search page: + https://www.sec.gov/edgar/search/#/q=%2522Insider%2520trading%2520report%2522 + +is functionally equivalent to our generated URL: + https://www.sec.gov/edgar/search/#/q=%22Insider%20trading%20report%20 +""" + +import datetime +import pytest + +from edgar_tool import url_generator + + +def test_should_correctly_generate_search_url_for_single_word(): + """Baseline test to assert that querying for a single word + produces the correct search URL""" + # GIVEN + keywords = ["10-K"] + expected_url = f"https://efts.sec.gov/LATEST/search-index?q=10-K" + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs({"keywords": keywords}) + + # THEN + assert actual_url == expected_url + + +def test_should_correctly_generate_search_url_for_exact_phrase(): + # GIVEN + keywords = ["Insider trading report"] + expected_url = ( + "https://efts.sec.gov/LATEST/search-index?q=%22Insider%20trading%20report%22" + ) + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs({"keywords": keywords}) + + # THEN + assert actual_url == expected_url + + +@pytest.mark.parametrize( + "test_kwarg", + [ + {"keywords": []}, + {"entity": []}, + ], +) +def test_should_raise_if_keywords_or_entity_missing(test_kwarg): + # GIVEN + expected_error_msg = ( + "Invalid search arguments. You must provide keywords or an entity." + ) + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwarg) + + +@pytest.mark.parametrize( + "date_kwarg", + [ + {"start_date": datetime.date.today()}, + {"end_date": datetime.date.today()}, + ], +) +def test_should_raise_if_date_range_custom_but_missing_dates(date_kwarg): + # GIVEN + expected_error_msg = ( + "Invalid date parameters. " + "You must provide both a start and end date if searching a custom date range." + ) + base_kwargs = {"keywords": ["Ford Motor Co"], "date_range_select": "custom"} + test_kwargs = {**base_kwargs, **date_kwarg} + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwargs) + + +def test_should_raise_if_date_range_select_invalid(): + # GIVEN + expected_error_msg = ( + "Invalid date_range_select. " + 'Value must be one of "all", "10y", "1y", "30d", or "custom"' + ) + test_kwargs = {"keywords": ["Ford Motor Co"], "date_range_select": "1m"} + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwargs) + + +@pytest.mark.parametrize( + "date_kwargs,url_ending", + [ + ( + { + "date_range_select": "custom", + "start_date": datetime.date.fromisoformat("2024-07-10"), + "end_date": datetime.date.fromisoformat("2024-07-15"), + }, + "&dateRange=custom&startdt=2024-07-10&enddt=2024-07-15", + ), + ({"date_range_select": "all"}, "&dateRange=all"), + ({"date_range_select": "10y"}, "&dateRange=10y"), + ({"date_range_select": "1y"}, "&dateRange=1y"), + ({"date_range_select": "30d"}, "&dateRange=30d"), + ], +) +def test_generates_correct_url_for_date_ranges(date_kwargs, url_ending): + """Tests that various date range options are correctly translated + into the seach URL.""" + # GIVEN + expected_url = f"https://efts.sec.gov/LATEST/search-index?q=%22Ford%20Motor%20Co%22{url_ending}" + test_kwargs = {**{"keywords": ["Ford Motor Co"]}, **date_kwargs} + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs(test_kwargs) + + # THEN + assert actual_url == expected_url + + +@pytest.mark.parametrize( + "filing_category, url_ending", + ( + ("all", ""), + ("all_except_section_16", "&category=form-cat0"), + ("all_annual_quarterly_and_current_reports", "&category=form-cat1"), + ("all_section_16", "&category=form-cat2"), + ("beneficial_ownership_reports", "&category=form-cat3"), + ("exempt_offerings", "&category=form-cat4"), + ("registration_statements", "&category=form-cat5"), + ("filing_review_correspondence", "&category=form-cat6"), + ("sec_orders_and_notices", "&category=form-cat7"), + ("proxy_materials", "&category=form-cat8"), + ("tender_offers_and_going_private_tx", "&category=form-cat9"), + ("trust_indentures", "&category=form-cat10"), + ), +) +def test_generates_correct_url_for_filing_category(filing_category, url_ending): + # GIVEN + expected_url = f"https://efts.sec.gov/LATEST/search-index?q=Ignore{url_ending}" + test_kwargs = {"keywords": ["Ignore"], "filing_category": filing_category} + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs(test_kwargs) + + # THEN + assert actual_url == expected_url + + +@pytest.mark.parametrize( + "single_forms, url_ending", + ( + (["1"], "&forms=1"), + (["CORRESP"], "&forms=CORRESP"), + ( + ["F-4, PREC14A, SEC STAFF ACTION"], + "&forms=F-4%2C%20PREC14A%2C%20SEC%20STAFF%20ACTION", + ), + ), +) +def test_generates_correct_url_for_single_forms(single_forms, url_ending): + # GIVEN + expected_url = ( + f"https://efts.sec.gov/LATEST/search-index?q=Ignore&category=custom{url_ending}" + ) + test_kwargs = {"keywords": ["Ignore"], "single_forms": single_forms} + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs(test_kwargs) + + # THEN + assert actual_url == expected_url + + +def test_raises_an_exception_if_user_passes_both_filing_category_and_single_forms(): + """When a user filters based on single form type the filing category is automatically + set to "custom." Therefore passing a filing category when using single forms both does + not make sense and will potentially give the user confusing results if the code ignores + the passed filing category and sets it as custom. It's best to raise an error and let + the user use either a filing category or single forms. + """ + # GIVEN + test_kwargs = { + "keywords": ["Ignore"], + "single_forms": ["F-4, PREC14A, SEC STAFF ACTION"], + "filing_category": "beneficial_ownership_reports", + } + expected_error_msg = ( + "Cannot specify both filing_category and single_forms. " + "Passing single_forms automatically sets the filing_category" + " to custom. Please choose one or the other." + ) + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwargs) + + +@pytest.mark.parametrize( + "abbreviation, expected_location_code", + [ + # US States - All use 2-letter state & territory abbreviations (ISO 3166-2) + ("AL", "AL"), # Alabama + ("AK", "AK"), # Alaska + ("AZ", "AZ"), # Arizona + ("AR", "AR"), # Arkansas + ("CA", "CA"), # California + ("CO", "CO"), # Colorado + ("CT", "CT"), # Connecticut + ("DE", "DE"), # Delaware + ("DC", "DC"), # District of Columbia + ("FL", "FL"), # Florida + ("GA", "GA"), # Georgia + ("HI", "HI"), # Hawaii + ("ID", "ID"), # Idaho + ("IL", "IL"), # Illinois + ("IN", "IN"), # Indiana + ("IA", "IA"), # Iowa + ("KS", "KS"), # Kansas + ("KY", "KY"), # Kentucky + ("LA", "LA"), # Louisiana + ("ME", "ME"), # Maine + ("MD", "MD"), # Maryland + ("MA", "MA"), # Massachusetts + ("MI", "MI"), # Michigan + ("MN", "MN"), # Minnesota + ("MS", "MS"), # Mississippi + ("MO", "MO"), # Missouri + ("MT", "MT"), # Montana + ("NE", "NE"), # Nebraska + ("NV", "NV"), # Nevada + ("NH", "NH"), # New Hampshire + ("NJ", "NJ"), # New Jersey + ("NM", "NM"), # New Mexico + ("NY", "NY"), # New York + ("NC", "NC"), # North Carolina + ("ND", "ND"), # North Dakota + ("OH", "OH"), # Ohio + ("OK", "OK"), # Oklahoma + ("OR", "OR"), # Oregon + ("PA", "PA"), # Pennsylvania + ("RI", "RI"), # Rhode Island + ("SC", "SC"), # South Carolina + ("SD", "SD"), # South Dakota + ("TN", "TN"), # Tennessee + ("TX", "TX"), # Texas + ("UT", "UT"), # Utah + ("VT", "VT"), # Vermont + ("VA", "VA"), # Virginia + ("WA", "WA"), # Washington + ("WV", "WV"), # West Virginia + ("WI", "WI"), # Wisconsin + ("WY", "WY"), # Wyoming + # Canadian Provinces - P.E.O. in to use internationally approved alpha codes (ISO 3166-2) + ("AB", "A0"), # Alberta + ("BC", "A1"), # British Columbia + ("CAN", "Z4"), # Canada (Federal Level) + ("MB", "A2"), # Manitoba + ("NB", "A3"), # New Brunswick + ("NL", "A4"), # Newfoundland and Labrador + ("NS", "A5"), # Nova Scotia + ("ON", "A6"), # Ontario + ("PE", "A7"), # Prince Edward Island + ("QC", "A8"), # Quebec + ("SK", "A9"), # Saskatchewan + ("YT", "B0"), # Yukon + # Other Countries - All use internationally approved 3-letter alpha codes (ISO 3166-1) + ("AFG", "B2"), # Afghanistan + ("ALA", "Y6"), # Aland Islands + ("ALB", "B3"), # Albania + ("DZA", "B4"), # Algeria + ("ASM", "B5"), # American Samoa + ("AND", "B6"), # Andorra + ("AGO", "B7"), # Angola + ("AIA", "1A"), # Anguilla + ("ATA", "B8"), # Antarctica + ("ATG", "B9"), # Antigua and Barbuda + ("ARG", "C1"), # Argentina + ("ARM", "1B"), # Armenia + ("ABW", "1C"), # Aruba + ("AUS", "C3"), # Australia + ("AUT", "C4"), # Austria + ("AZE", "1D"), # Azerbaijan + ("BHS", "C5"), # Bahamas + ("BHR", "C6"), # Bahrain + ("BGD", "C7"), # Bangladesh + ("BRB", "C8"), # Barbados + ("BLR", "1F"), # Belarus + ("BEL", "C9"), # Belgium + ("BLZ", "D1"), # Belize + ("BEN", "G6"), # Benin + ("BMU", "D0"), # Bermuda + ("BTN", "D2"), # Bhutan + ("BOL", "D3"), # Bolivia + ("BIH", "1E"), # Bosnia and Herzegovina + ("BWA", "B1"), # Botswana + ("BVT", "D4"), # Bouvet Island + ("BRA", "D5"), # Brazil + ("IOT", "D6"), # British Indian Ocean Territory + ("BRN", "D9"), # Brunei Darussalam + ("BGR", "E0"), # Bulgaria + ("BFA", "X2"), # Burkina Faso + ("BDI", "E2"), # Burundi + ("KHM", "E3"), # Cambodia + ("CMR", "E4"), # Cameroon + ("CPV", "E8"), # Cape Verde + ("CYM", "E9"), # Cayman Islands + ("CAF", "F0"), # Central African Republic + ("TCD", "F2"), # Chad + ("CHL", "F3"), # Chile + ("CHN", "F4"), # China + ("CXR", "F6"), # Christmas Island + ("CCK", "F7"), # Cocos (Keeling) Islands + ("COL", "F8"), # Colombia + ("COM", "F9"), # Comoros + ("COG", "G0"), # Congo + ("COD", "Y3"), # Congo, Democratic Republic of the + ("COK", "G1"), # Cook Islands + ("CRI", "G2"), # Costa Rica + ("CIV", "L7"), # Cote d'Ivoire + ("HRV", "1M"), # Croatia + ("CUB", "G3"), # Cuba + ("CYP", "G4"), # Cyprus + ("CZE", "2N"), # Czech Republic + ("DNK", "G7"), # Denmark + ("DJI", "1G"), # Djibouti + ("DMA", "G9"), # Dominica + ("DOM", "D8"), # Dominican Republic + ("ECU", "H1"), # Ecuador + ("EGY", "H2"), # Egypt + ("SLV", "H3"), # El Salvador + ("GNQ", "H4"), # Equatorial Guinea + ("ERI", "1J"), # Eritrea + ("EST", "1H"), # Estonia + ("ETH", "H5"), # Ethiopia + ("FLK", "H7"), # Falkland Islands (Malvinas) + ("FRO", "H6"), # Faroe Islands + ("FJI", "H8"), # Fiji + ("FIN", "H9"), # Finland + ("FRA", "I0"), # France + ("GUF", "I3"), # French Guiana + ("PYF", "I4"), # French Polynesia + ("ATF", "2C"), # French Southern Territories + ("GAB", "I5"), # Gabon + ("GMB", "I6"), # Gambia + ("GEO", "2Q"), # Georgia + ("DEU", "2M"), # Germany + ("GHA", "J0"), # Ghana + ("GIB", "J1"), # Gibraltar + ("GRC", "J3"), # Greece + ("GRL", "J4"), # Greenland + ("GRD", "J5"), # Grenada + ("GLP", "J6"), # Guadeloupe + ("GUM", "GU"), # Guam + ("GTM", "J8"), # Guatemala + ("GGY", "Y7"), # Guernsey + ("GIN", "J9"), # Guinea + ("GNB", "S0"), # Guinea-Bissau + ("GUY", "K0"), # Guyana + ("HTI", "K1"), # Haiti + ("HMD", "K4"), # Heard Island and McDonald Islands + ("VAT", "X4"), # Holy See (Vatican City State) + ("HND", "K2"), # Honduras + ("HKG", "K3"), # Hong Kong + ("HUN", "K5"), # Hungary + ("ISL", "K6"), # Iceland + ("IND", "K7"), # India + ("IDN", "K8"), # Indonesia + ("IRN", "K9"), # Iran + ("IRQ", "L0"), # Iraq + ("IRL", "L2"), # Ireland + ("IMN", "Y8"), # Isle of Man + ("ISR", "L3"), # Israel + ("ITA", "L6"), # Italy + ("JAM", "L8"), # Jamaica + ("JPN", "M0"), # Japan + ("JEY", "Y9"), # Jersey + ("JOR", "M2"), # Jordan + ("KAZ", "1P"), # Kazakhstan + ("KEN", "M3"), # Kenya + ("KIR", "J2"), # Kiribati + ("PRK", "M4"), # Korea, Democratic People's Republic of + ("KOR", "M5"), # Korea, Republic of + ("KWT", "M6"), # Kuwait + ("KGZ", "1N"), # Kyrgyzstan + ("LAO", "M7"), # Lao People's Democratic Republic + ("LVA", "1R"), # Latvia + ("LBN", "M8"), # Lebanon + ("LSO", "M9"), # Lesotho + ("LBR", "N0"), # Liberia + ("LBY", "N1"), # Libya + ("LIE", "N2"), # Liechtenstein + ("LTU", "1Q"), # Lithuania + ("LUX", "N4"), # Luxembourg + ("MAC", "N5"), # Macao + ("MKD", "1U"), # Macedonia + ("MDG", "N6"), # Madagascar + ("MWI", "N7"), # Malawi + ("MYS", "N8"), # Malaysia + ("MDV", "N9"), # Maldives + ("MLI", "O0"), # Mali + ("MLT", "O1"), # Malta + ("MHL", "1T"), # Marshall Islands + ("MTQ", "O2"), # Martinique + ("MRT", "O3"), # Mauritania + ("MUS", "O4"), # Mauritius + ("MYT", "2P"), # Mayotte + ("MEX", "O5"), # Mexico + ("FSM", "1K"), # Micronesia, Federated States of + ("MDA", "1S"), # Moldova + ("MCO", "O9"), # Monaco + ("MNG", "P0"), # Mongolia + ("MNE", "Z5"), # Montenegro + ("MSR", "P1"), # Montserrat + ("MAR", "P2"), # Morocco + ("MOZ", "P3"), # Mozambique + ("MMR", "E1"), # Myanmar + ("NAM", "T6"), # Namibia + ("NRU", "P5"), # Nauru + ("NPL", "P6"), # Nepal + ("NLD", "P7"), # Netherlands + ("ANT", "P8"), # Netherlands Antilles + ("NCL", "1W"), # New Caledonia + ("NZL", "Q2"), # New Zealand + ("NIC", "Q3"), # Nicaragua + ("NER", "Q4"), # Niger + ("NGA", "Q5"), # Nigeria + ("NIU", "Q6"), # Niue + ("NFK", "Q7"), # Norfolk Island + ("MNP", "1V"), # Northern Mariana Islands + ("NOR", "Q8"), # Norway + ("OMN", "P4"), # Oman + ("PAK", "R0"), # Pakistan + ("PLW", "1Y"), # Palau + ("PSE", "1X"), # Palestinian Territory + ("PAN", "R1"), # Panama + ("PNG", "R2"), # Papua New Guinea + ("PRY", "R4"), # Paraguay + ("PER", "R5"), # Peru + ("PHL", "R6"), # Philippines + ("PCN", "R8"), # Pitcairn + ("POL", "R9"), # Poland + ("PRT", "S1"), # Portugal + ("PRI", "PR"), # Puerto Rico + ("QAT", "S3"), # Qatar + ("REU", "S4"), # Reunion + ("ROU", "S5"), # Romania + ("RUS", "1Z"), # Russian Federation + ("RWA", "S6"), # Rwanda + ("BLM", "Z0"), # Saint Barthelemy + ("SHN", "U8"), # Saint Helena + ("KNA", "U7"), # Saint Kitts and Nevis + ("LCA", "U9"), # Saint Lucia + ("MAF", "Z1"), # Saint Martin + ("SPM", "V0"), # Saint Pierre and Miquelon + ("VCT", "V1"), # Saint Vincent and the Grenadines + ("WSM", "Y0"), # Samoa + ("SMR", "S8"), # San Marino + ("STP", "S9"), # Sao Tome and Principe + ("SAU", "T0"), # Saudi Arabia + ("SEN", "T1"), # Senegal + ("SRB", "Z2"), # Serbia + ("SYC", "T2"), # Seychelles + ("SLE", "T8"), # Sierra Leone + ("SGP", "U0"), # Singapore + ("SVK", "2B"), # Slovakia + ("SVN", "2A"), # Slovenia + ("SLB", "D7"), # Solomon Islands + ("SOM", "U1"), # Somalia + ("ZAF", "T3"), # South Africa + ("SGS", "1L"), # South Georgia and the South Sandwich Islands + ("ESP", "U3"), # Spain + ("LKA", "F1"), # Sri Lanka + ("SDN", "V2"), # Sudan + ("SUR", "V3"), # Suriname + ("SJM", "L9"), # Svalbard and Jan Mayen + ("SWZ", "V6"), # Kingdom of Eswatini (Formerly Swaziland) + ("SWE", "V7"), # Sweden + ("CHE", "V8"), # Switzerland + ("SYR", "V9"), # Syrian Arab Republic (Syria) + ("TWN", "F5"), # Taiwan + ("TJK", "2D"), # Tajikistan + ("THA", "W1"), # Thailand + ("TLS", "Z3"), # Timor-Leste + ("TGO", "W2"), # Togo + ("TKL", "W3"), # Tokelau + ("TON", "W4"), # Tonga + ("TTO", "W5"), # Trinidad and Tobago + ("TUN", "W6"), # Tunisia + ("TUR", "W8"), # Turkey + ("TKM", "2E"), # Turkmenistan + ("TCA", "W7"), # Turks and Caicos Islands + ("TUV", "2G"), # Tuvalu + ("UGA", "W9"), # Uganda + ("UKR", "2H"), # Ukraine + ("ARE", "C0"), # United Arab Emirates + ("GBR", "X0"), # United Kingdom + ("UMI", "2J"), # United States Minor Outlying Islands + ("URY", "X3"), # Uruguay + ("UZB", "2K"), # Uzbekistan + ("VUT", "2L"), # Vanuatu + ("VEN", "X5"), # Venezuela + ("VNM", "Q1"), # Vietnam + ("VGB", "D8"), # British Virgin Islands + ("VIR", "VI"), # U.S. Virgin Islands + ("WLF", "X8"), # Wallis and Futuna + ("ESH", "Y1"), # Western Sahara + ("YEM", "T7"), # Yemen + ("ZMB", "Y4"), # Zambia + ("ZWE", "Y5"), # Zimbabwe + ("XX", "XX"), # Unknown + ], +) +class TestPeoInAndIncIn: + def test_should_correctly_generate_search_url_for_peo_in( + self, abbreviation, expected_location_code + ): + # GIVEN + expected_url = f"https://efts.sec.gov/LATEST/search-index?q=a&locationCode={expected_location_code}" + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs( + {"keywords": ["a"], "peo_in": abbreviation} + ) + + # THEN + assert actual_url == expected_url + + def test_should_correctly_generate_search_url_for_inc_in( + self, abbreviation, expected_location_code + ): + # GIVEN + expected_url = f"https://efts.sec.gov/LATEST/search-index?q=a&locationType=incorporated&locationCode={expected_location_code}" + + # WHEN + actual_url = url_generator.generate_search_url_for_kwargs( + {"keywords": ["a"], "inc_in": abbreviation} + ) + + # THEN + assert actual_url == expected_url + + +@pytest.mark.parametrize("key", ["peo_in", "inc_in"]) +def test_should_raise_exception_if_location_code_invalid(key): + # GIVEN + expected_error_msg = ( + "Invalid location code. " + "Please provide a valid 2-letter state abbreviation, " + "3-letter country code, or 'XX' for unknown." + ) + soviet_union = "SUN" + test_kwargs = {"keywords": ["a"], key: soviet_union} + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwargs) + + +def test_should_raise_exception_if_both_peo_in_and_inc_in(): + # GIVEN + expected_error_msg = ( + "Cannot specify both peo_in and inc_in. Please choose one or the other." + ) + + test_kwargs = {"keywords": ["a"], "peo_in": "CA", "inc_in": "CA"} + + # WHEN / THEN + with pytest.raises(ValueError, match=expected_error_msg): + url_generator.generate_search_url_for_kwargs(test_kwargs)