From 623a64d501bfb8668b805097dd23439a6e0b06c2 Mon Sep 17 00:00:00 2001 From: Eddy Hintze Date: Wed, 31 Jul 2024 19:59:58 -0400 Subject: [PATCH] formatting change. replaced setuptools with poetry --- .markdownlint.yaml | 191 ++++++++ .pre-commit-config.yaml | 25 ++ CHANGELOG.md | 69 ++- README.md | 32 +- humblebundle_downloader/_version.py | 1 - humblebundle_downloader/cli.py | 75 ++-- humblebundle_downloader/download_library.py | 294 +++++++----- poetry.lock | 469 ++++++++++++++++++++ pyproject.toml | 26 ++ setup.py | 34 -- tests/test_cli.py | 8 +- tests/test_download_library.py | 64 +-- 12 files changed, 1037 insertions(+), 251 deletions(-) create mode 100644 .markdownlint.yaml create mode 100644 .pre-commit-config.yaml delete mode 100644 humblebundle_downloader/_version.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..cf6489d --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,191 @@ +# Example markdownlint configuration with all properties set to their default value + +# Default state for all rules +default: true + +# MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md001.md +MD001: true + +# MD007/ul-indent : Unordered list indentation : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md007.md +MD007: + # Spaces for indent + indent: 4 + # Whether to indent the first level of the list + start_indented: false + # Spaces for first level indent (when start_indented is set) + start_indent: 4 + +# MD009/no-trailing-spaces : Trailing spaces : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md009.md +MD009: + # Spaces for line break + br_spaces: 2 + # Allow spaces for empty lines in list items + list_item_empty_lines: false + # Include unnecessary breaks + strict: false + +# MD010/no-hard-tabs : Hard tabs : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md010.md +MD010: + # Include code blocks + code_blocks: true + # Fenced code languages to ignore + ignore_code_languages: [] + # Number of spaces for each hard tab + spaces_per_tab: 4 + +# MD012/no-multiple-blanks : Multiple consecutive blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md012.md +MD012: + # Consecutive blank lines + maximum: 2 + +# MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md013.md +MD013: + # Number of characters + line_length: 119 + # Number of characters for headings + heading_line_length: 119 + # Number of characters for code blocks + code_block_line_length: 119 + # Include code blocks + code_blocks: true + # Include tables + tables: true + # Include headings + headings: true + # Strict length checking + strict: false + # Stern length checking + stern: false + +# MD022/blanks-around-headings : Headings should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md022.md +MD022: + # Blank lines above heading + lines_above: 2 + # Blank lines below heading + lines_below: 1 + +# MD023/heading-start-left : Headings must start at the beginning of the line : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md023.md +MD023: true + +# MD025/single-title/single-h1 : Multiple top-level headings in the same document : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md025.md +MD025: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD026/no-trailing-punctuation : Trailing punctuation in heading : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md026.md +MD026: + # Punctuation characters + punctuation: ".,;:!。,;:!" + +# MD027/no-multiple-space-blockquote : Multiple spaces after blockquote symbol : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md027.md +MD027: true + +# MD028/no-blanks-blockquote : Blank line inside blockquote : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md028.md +MD028: true + +# MD029/ol-prefix : Ordered list item prefix : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md029.md +MD029: + # List style + style: "ordered" + +# MD031/blanks-around-fences : Fenced code blocks should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md031.md +MD031: + # Include list items + list_items: true + +# MD032/blanks-around-lists : Lists should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md032.md +MD032: true + +# MD034/no-bare-urls : Bare URL used : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md034.md +MD034: true + +# MD036/no-emphasis-as-heading : Emphasis used instead of a heading : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md036.md +MD036: + # Punctuation characters + punctuation: ".,;:!?。,;:!?" + +# MD037/no-space-in-emphasis : Spaces inside emphasis markers : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md037.md +MD037: true + +# MD038/no-space-in-code : Spaces inside code span elements : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md038.md +MD038: true + +# MD039/no-space-in-links : Spaces inside link text : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md039.md +MD039: true + +# MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md040.md +MD040: + # Require language only + language_only: true + +# MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md041.md +MD041: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md042.md +MD042: true + +# MD046/code-block-style : Code block style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md046.md +MD046: + # Block style + style: "consistent" + +# MD047/single-trailing-newline : Files should end with a single newline character : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md047.md +MD047: true + +# MD048/code-fence-style : Code fence style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md048.md +MD048: + # Code fence style + style: "consistent" + +# MD049/emphasis-style : Emphasis style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md049.md +MD049: + # Emphasis style + style: "consistent" + +# MD050/strong-style : Strong style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md050.md +MD050: + # Strong style + style: "consistent" + +# MD051/link-fragments : Link fragments should be valid : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md051.md +MD051: true + +# MD052/reference-links-images : Reference links and images should use a label that is defined : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md052.md +MD052: + # Include shortcut syntax + shortcut_syntax: false + +# MD053/link-image-reference-definitions : Link and image reference definitions should be needed : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md053.md +MD053: + # Ignored definitions + ignored_definitions: + - "//" + +# MD054/link-image-style : Link and image style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md054.md +MD054: + # Allow autolinks + autolink: true + # Allow inline links and images + inline: true + # Allow full reference links and images + full: true + # Allow collapsed reference links and images + collapsed: true + # Allow shortcut reference links and images + shortcut: true + # Allow URLs as inline links + url_inline: true + +# MD055/table-pipe-style : Table pipe style : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md055.md +MD055: + # Table pipe style + style: "consistent" + +# MD056/table-column-count : Table column count : https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md056.md +MD056: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6135c38 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.3.2 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.39.0 + hooks: + - id: markdownlint + args: ["-f"] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: check-merge-conflict + - id: mixed-line-ending diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e2f3b..7158c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,76 +1,101 @@ # Change log -### 0.4.0 +## 0.4.1 + +- Fixed crash when missing cli args ([#48](https://github.com/xtream1101/humblebundle-downloader/pull/48)) +- Updated the Trove url ([#59](https://github.com/xtream1101/humblebundle-downloader/pull/59)) +- Using [pre-commit](https://pre-commit.com/) hooks for formatting and linting +- Moved from setuptools to poetry for packaging + + +## 0.4.0 + - Deprecate the `download` argument. It is no longer needed since that is the only action that can be taken -### 0.3.4 +## 0.3.4 + - Merged in [PR 35](https://github.com/xtream1101/humblebundle-downloader/pull/35) to fix some trove games not downloading -### 0.3.3 +## 0.3.3 + - Fixed crashing when file is missing on humblebundle - Updated cookie info in readme - - Supports passing in the cookie value of `_simpleauth_sess` by using `--session-auth` + - Supports passing in the cookie value of `_simpleauth_sess` by using `--session-auth` -### 0.3.1 +## 0.3.1 + - Added support for netscape cookies - -### 0.3.0 + +## 0.3.0 + - pip install now requires python version 3.4+ - `--trove` will only download trove products, nothing else - Filtering flags now work when downloading trove content -### 0.2.2 +## 0.2.2 + - Confirm the download is complete by checking the expected size to what downloaded - Fixed the platform filter -### 0.2.1 +## 0.2.1 + - Fixed include & exclude logic being switched in v0.2.0 -### 0.2.0 +## 0.2.0 + - Added **Humble Trove** support _(`--trove` to also check/download trove content)_ - Now by default only new content is downloaded. Use `--update` to also check for updated content -### 0.1.3 +## 0.1.3 + - Fixed re-downloading for real this time - - Only use the url last modified time as the check for new versions + - Only use the url last modified time as the check for new versions -### 0.1.2 +## 0.1.2 + - Stop using md5 & sha1 hashes to check if file is unique (was creating duplicate downloads of the same file) - Strip periods from end of directory & file names - Rename older versions of a file before download the new one -### 0.1.1 +## 0.1.1 + - Delete failed downloaded files - -### 0.1.0 + +## 0.1.0 + - Filename saved is now the original name of the file -- key used in cache is different due to changing the file name (this may result in duplicate downloads if you have run the older version) -- Support for downloading a single Bundle/Purchase by using the flag `-k` or `--key` and getting the key from the url of a purchase +- key used in cache is different due to changing the file name + - _this may result in duplicate downloads if you have run the older version_ +- Support for downloading a single Bundle/Purchase by using the + flag `-k` or `--key` and getting the key from the url of a purchase -### 0.0.8 +## 0.0.8 + - gen-cookies now works with SSO feature and 2FA logins - Added `--include` & `--exclude` cli args to filter file types -### 0.0.7 -- Replace `:` with ` -` in filenames +## 0.0.7 + +- Replace `:` with `-` in filenames - Ignore items that do not have a web url -### 0.0.6 +## 0.0.6 + - Started change log - Added more detail to readme - Removed the use of f-strings to support more python versions diff --git a/README.md b/README.md index 690a76a..4351765 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Humble Bundle Downloader -[![PyPI](https://img.shields.io/pypi/v/humblebundle-downloader.svg)](https://pypi.python.org/pypi/humblebundle-downloader) -[![PyPI](https://img.shields.io/pypi/l/humblebundle-downloader.svg)](https://pypi.python.org/pypi/humblebundle-downloader) +[![PyPI](https://img.shields.io/pypi/v/humblebundle-downloader.svg)](https://pypi.python.org/pypi/humblebundle-downloader) +[![PyPI](https://img.shields.io/pypi/l/humblebundle-downloader.svg)](https://pypi.python.org/pypi/humblebundle-downloader) **Download all of your content from your Humble Bundle Library!** -The first time this runs it may take a while because it will download everything. After that it will only download the content that has been updated or is missing. +The first time this runs it may take a while because it will download everything. +After that it will only download the content that has been updated or is missing. + ## Features + - support for Humble Trove _(`--trove` flag)_ - downloads new and updated content from your Humble Bundle Library on each run _(only check for updates if using `--update`)_ - cli command for easy use (downloading will also work on a headless system) @@ -18,23 +21,29 @@ The first time this runs it may take a while because it will download everything ## Install + `pip install humblebundle-downloader` ## Instructions + ### 1. Getting cookies -First thing to do is get your account cookies. This can be done by getting a browser extension that lets you see or export your cookies. + +First thing to do is get your account cookies. +This can be done by getting a browser extension that lets you see or export your cookies. - **Method 1 (recommended)** - Get the value of the cookie called `_simpleauth_sess` and pass that value using `-s "COOKIE_VALUE"` - **Method 2** - Export the cookies in the Netscape format using an extension. - If your exported cookie file is not working, it may be a formatting issue, this can be fixed by running the command `curl -b cookies.orig.txt --cookie-jar cookies.txt http://bogus` + If your exported cookie file is not working, it may be a formatting issue. + This can be fixed by running the command `curl -b cookies.orig.txt --cookie-jar cookies.txt http://bogus` ### 2. Downloading your library + Use the following command to download your Humble Bundle Library: `hbd --cookie-file cookies.txt --library-path "Downloaded Library" --progress` @@ -43,7 +52,12 @@ This directory structure will be used: ## Notes -* Inside your library folder a file named `.cache.json` is saved and keeps track of the files that have been downloaded. This way running the download command again pointing to the same directory will only download new or updated files. -* Use `--help` with all `hbd` commands to see available options -* Find supported platforms for the `--platform` flag by visiting your Humble Bundle Library and look under the **Platform** dropdown -* Download select bundles by using the `-k` or `--keys` flag. Find these keys by going to your *Purchases* section, click on a products and there should be a `downloads?key=XXXX` in the url. + +- Inside your library folder a file named `.cache.json` is saved and keeps track of the files that have been downloaded. + This way running the download command again pointing to the same directory will only download new or updated files. +- Use `--help` with all `hbd` commands to see available options +- Find supported platforms for the `--platform` flag by visiting your Humble Bundle Library + and look under the **Platform** dropdown +- Download select bundles by using the `-k` or `--keys` flag. + Find these keys by going to your _Purchases_ section, + click on a products and there should be a `downloads?key=XXXX` in the url. diff --git a/humblebundle_downloader/_version.py b/humblebundle_downloader/_version.py deleted file mode 100644 index abeeedb..0000000 --- a/humblebundle_downloader/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.4.0' diff --git a/humblebundle_downloader/cli.py b/humblebundle_downloader/cli.py index b964569..e4d2948 100644 --- a/humblebundle_downloader/cli.py +++ b/humblebundle_downloader/cli.py @@ -5,17 +5,17 @@ import argparse logger = logging.getLogger(__name__) -LOG_LEVEL = os.environ.get('HBD_LOGLEVEL', 'INFO').upper() +LOG_LEVEL = os.environ.get("HBD_LOGLEVEL", "INFO").upper() logging.basicConfig( level=LOG_LEVEL, - format='%(message)s', + format="%(message)s", ) # Ignore unwanted logs from the requests lib when debuging -logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) +logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING) def parse_args(args): - if len(args) > 0 and args[0].lower() == 'download': + if len(args) > 0 and args[0].lower() == "download": args = args[1:] raise DeprecationWarning("`download` argument is no longer used") @@ -23,55 +23,75 @@ def parse_args(args): cookie = parser.add_mutually_exclusive_group(required=True) cookie.add_argument( - '-c', '--cookie-file', type=str, + "-c", + "--cookie-file", + type=str, help="Location of the cookies file", ) cookie.add_argument( - '-s', '--session-auth', type=str, + "-s", + "--session-auth", + type=str, help="Value of the cookie _simpleauth_sess. WRAP IN QUOTES", ) parser.add_argument( - '-l', '--library-path', type=str, + "-l", + "--library-path", + type=str, help="Folder to download all content to", required=True, ) parser.add_argument( - '-t', '--trove', action='store_true', + "-t", + "--trove", + action="store_true", help="Only check and download Humble Trove content", ) parser.add_argument( - '-u', '--update', action='store_true', - help=("Check to see if products have been updated " - "(still get new products)"), + "-u", + "--update", + action="store_true", + help=("Check to see if products have been updated " "(still get new products)"), ) parser.add_argument( - '-p', '--platform', - type=str, nargs='*', - help=("Only get content in a platform. Values can be seen in your " - "humble bundle's library dropdown. Ex: -p ebook video"), + "-p", + "--platform", + type=str, + nargs="*", + help=( + "Only get content in a platform. Values can be seen in your " + "humble bundle's library dropdown. Ex: -p ebook video" + ), ) parser.add_argument( - '--progress', - action='store_true', + "--progress", + action="store_true", help="Display progress bar for downloads", ) filter_ext = parser.add_mutually_exclusive_group() filter_ext.add_argument( - '-e', '--exclude', - type=str, nargs='*', - help=("File extensions to ignore when downloading files. " - "Ex: -e pdf mobi"), + "-e", + "--exclude", + type=str, + nargs="*", + help=("File extensions to ignore when downloading files. " "Ex: -e pdf mobi"), ) filter_ext.add_argument( - '-i', '--include', - type=str, nargs='*', + "-i", + "--include", + type=str, + nargs="*", help="Only download files with these extensions. Ex: -i pdf mobi", ) parser.add_argument( - '-k', '--keys', - type=str, nargs='*', - help=("The purchase download key. Find in the url on the " - "products/bundle download page. Can set multiple"), + "-k", + "--keys", + type=str, + nargs="*", + help=( + "The purchase download key. Find in the url on the " + "products/bundle download page. Can set multiple" + ), ) return parser.parse_args(args) @@ -81,6 +101,7 @@ def cli(): cli_args = parse_args(sys.argv[1:]) from .download_library import DownloadLibrary + DownloadLibrary( cli_args.library_path, cookie_path=cli_args.cookie_file, diff --git a/humblebundle_downloader/download_library.py b/humblebundle_downloader/download_library.py index 1356bfd..1c07f0c 100644 --- a/humblebundle_downloader/download_library.py +++ b/humblebundle_downloader/download_library.py @@ -12,27 +12,39 @@ logger = logging.getLogger(__name__) def _clean_name(dirty_str): - allowed_chars = (' ', '_', '.', '-', '[', ']') + allowed_chars = (" ", "_", ".", "-", "[", "]") clean = [] - for c in dirty_str.replace('+', '_').replace(':', ' -'): + for c in dirty_str.replace("+", "_").replace(":", " -"): if c.isalpha() or c.isdigit() or c in allowed_chars: clean.append(c) - return "".join(clean).strip().rstrip('.') + return "".join(clean).strip().rstrip(".") class DownloadLibrary: - - def __init__(self, library_path, cookie_path=None, cookie_auth=None, - progress_bar=False, ext_include=None, ext_exclude=None, - platform_include=None, purchase_keys=None, trove=False, - update=False): + def __init__( + self, + library_path, + cookie_path=None, + cookie_auth=None, + progress_bar=False, + ext_include=None, + ext_exclude=None, + platform_include=None, + purchase_keys=None, + trove=False, + update=False, + ): self.library_path = library_path self.progress_bar = progress_bar - self.ext_include = [] if ext_include is None else list(map(str.lower, ext_include)) # noqa: E501 - self.ext_exclude = [] if ext_exclude is None else list(map(str.lower, ext_exclude)) # noqa: E501 + self.ext_include = ( + [] if ext_include is None else list(map(str.lower, ext_include)) + ) + self.ext_exclude = ( + [] if ext_exclude is None else list(map(str.lower, ext_exclude)) + ) - if platform_include is None or 'all' in platform_include: + if platform_include is None or "all" in platform_include: # if 'all', then do not need to use this check platform_include = [] self.platform_include = list(map(str.lower, platform_include)) @@ -49,23 +61,24 @@ class DownloadLibrary: self.session.cookies = cookie_jar except http.cookiejar.LoadError: # Still support the original cookie method - with open(cookie_path, 'r') as f: - self.session.headers.update({'cookie': f.read().strip()}) + with open(cookie_path, "r") as f: + self.session.headers.update({"cookie": f.read().strip()}) elif cookie_auth: self.session.headers.update( - {'cookie': '_simpleauth_sess={}'.format(cookie_auth)} + {"cookie": "_simpleauth_sess={}".format(cookie_auth)} ) def start(self): - - self.cache_file = os.path.join(self.library_path, '.cache.json') + self.cache_file = os.path.join(self.library_path, ".cache.json") self.cache_data = self._load_cache_data(self.cache_file) - self.purchase_keys = self.purchase_keys if self.purchase_keys else self._get_purchase_keys() # noqa: E501 + self.purchase_keys = ( + self.purchase_keys if self.purchase_keys else self._get_purchase_keys() + ) if self.trove is True: logger.info("Only checking the Humble Trove...") for product in self._get_trove_products(): - title = _clean_name(product['human-name']) + title = _clean_name(product["human-name"]) self._process_trove_product(title, product) else: for order_id in self.purchase_keys: @@ -74,50 +87,56 @@ class DownloadLibrary: def _get_trove_download_url(self, machine_name, web_name): try: sign_r = self.session.post( - 'https://www.humblebundle.com/api/v1/user/download/sign', + "https://www.humblebundle.com/api/v1/user/download/sign", data={ - 'machine_name': machine_name, - 'filename': web_name, + "machine_name": machine_name, + "filename": web_name, }, ) except Exception: - logger.error("Failed to get download url for trove product {title}" - .format(title=web_name)) + logger.error( + "Failed to get download url for trove product {title}".format( + title=web_name + ) + ) return None logger.debug("Signed url response {sign_r}".format(sign_r=sign_r)) - if sign_r.json().get('_errors') == 'Unauthorized': + if sign_r.json().get("_errors") == "Unauthorized": logger.critical("Your account does not have access to the Trove") sys.exit() - signed_url = sign_r.json()['signed_url'] + signed_url = sign_r.json()["signed_url"] logger.debug("Signed url {signed_url}".format(signed_url=signed_url)) return signed_url def _process_trove_product(self, title, product): - for platform, download in product['downloads'].items(): + for platform, download in product["downloads"].items(): # Sometimes the name has a dir in it # Example is "Broken Sword 5 - the Serpent's Curse" # Only the windows file has a dir like # "revolutionsoftware/BS5_v2.2.1-win32.zip" - if self._should_download_platform(platform) is False: # noqa: E501 - logger.info("Skipping {platform} for {product_title}" - .format(platform=platform, - product_title=title)) + if self._should_download_platform(platform) is False: + logger.info( + "Skipping {platform} for {product_title}".format( + platform=platform, product_title=title + ) + ) continue - web_name = download['url']['web'].split('/')[-1] - ext = web_name.split('.')[-1] + web_name = download["url"]["web"].split("/")[-1] + ext = web_name.split(".")[-1] if self._should_download_file_type(ext) is False: - logger.info("Skipping the file {web_name}" - .format(web_name=web_name)) + logger.info("Skipping the file {web_name}".format(web_name=web_name)) continue - cache_file_key = 'trove:{name}'.format(name=web_name) + cache_file_key = "trove:{name}".format(name=web_name) file_info = { - 'uploaded_at': (download.get('uploaded_at') - or download.get('timestamp') - or product.get('date_added', '0')), - 'md5': download.get('md5', 'UNKNOWN_MD5'), + "uploaded_at": ( + download.get("uploaded_at") + or download.get("timestamp") + or product.get("date_added", "0") + ), + "md5": download.get("md5", "UNKNOWN_MD5"), } cache_file_info = self.cache_data.get(cache_file_key, {}) @@ -125,20 +144,21 @@ class DownloadLibrary: # Do not care about checking for updates at this time continue - if (file_info['uploaded_at'] != cache_file_info.get('uploaded_at') - and file_info['md5'] != cache_file_info.get('md5')): - product_folder = os.path.join( - self.library_path, 'Humble Trove', title - ) + if file_info["uploaded_at"] != cache_file_info.get( + "uploaded_at" + ) and file_info["md5"] != cache_file_info.get("md5"): + product_folder = os.path.join(self.library_path, "Humble Trove", title) # Create directory to save the files to - try: os.makedirs(product_folder) # noqa: E701 - except OSError: pass # noqa: E701 + try: + os.makedirs(product_folder) + except OSError: + pass local_filename = os.path.join( product_folder, web_name, ) signed_url = self._get_trove_download_url( - download['machine_name'], + download["machine_name"], web_name, ) if signed_url is None: @@ -148,14 +168,14 @@ class DownloadLibrary: try: product_r = self.session.get(signed_url, stream=True) except Exception: - logger.error("Failed to get trove product {title}" - .format(title=web_name)) + logger.error( + "Failed to get trove product {title}".format(title=web_name) + ) continue - if 'uploaded_at' in cache_file_info: + if "uploaded_at" in cache_file_info: uploaded_at = time.strftime( - '%Y-%m-%d', - time.localtime(int(cache_file_info['uploaded_at'])) + "%Y-%m-%d", time.localtime(int(cache_file_info["uploaded_at"])) ) else: uploaded_at = None @@ -171,10 +191,11 @@ class DownloadLibrary: def _get_trove_products(self): trove_products = [] idx = 0 - trove_base_url = 'https://www.humblebundle.com/client/catalog?index={idx}' # noqa: E501 + trove_base_url = "https://www.humblebundle.com/client/catalog?index={idx}" while True: - logger.debug("Collecting trove product data from api pg:{idx} ..." - .format(idx=idx)) + logger.debug( + "Collecting trove product data from api pg:{idx} ...".format(idx=idx) + ) trove_page_url = trove_base_url.format(idx=idx) try: trove_r = self.session.get(trove_page_url) @@ -193,72 +214,80 @@ class DownloadLibrary: return trove_products def _process_order_id(self, order_id): - order_url = 'https://www.humblebundle.com/api/v1/order/{order_id}?all_tpkds=true'.format(order_id=order_id) # noqa: E501 + order_url = "https://www.humblebundle.com/api/v1/order/{order_id}?all_tpkds=true".format( + order_id=order_id + ) try: order_r = self.session.get( order_url, headers={ - 'content-type': 'application/json', - 'content-encoding': 'gzip', + "content-type": "application/json", + "content-encoding": "gzip", }, ) except Exception: - logger.error("Failed to get order key {order_id}" - .format(order_id=order_id)) + logger.error("Failed to get order key {order_id}".format(order_id=order_id)) return logger.debug("Order request: {order_r}".format(order_r=order_r)) order = order_r.json() - bundle_title = _clean_name(order['product']['human_name']) + bundle_title = _clean_name(order["product"]["human_name"]) logger.info("Checking bundle: " + str(bundle_title)) - for product in order['subproducts']: + for product in order["subproducts"]: self._process_product(order_id, bundle_title, product) def _rename_old_file(self, local_filename, append_str): # Check if older file exists, if so rename if os.path.isfile(local_filename) is True: - filename_parts = local_filename.rsplit('.', 1) - new_name = "{name}_{append_str}.{ext}"\ - .format(name=filename_parts[0], - append_str=append_str, - ext=filename_parts[1]) + filename_parts = local_filename.rsplit(".", 1) + new_name = "{name}_{append_str}.{ext}".format( + name=filename_parts[0], append_str=append_str, ext=filename_parts[1] + ) os.rename(local_filename, new_name) - logger.info("Renamed older file to {new_name}" - .format(new_name=new_name)) + logger.info("Renamed older file to {new_name}".format(new_name=new_name)) def _process_product(self, order_id, bundle_title, product): - product_title = _clean_name(product['human_name']) + product_title = _clean_name(product["human_name"]) # Get all types of download for a product - for download_type in product['downloads']: - if self._should_download_platform(download_type['platform']) is False: # noqa: E501 - logger.info("Skipping {platform} for {product_title}" - .format(platform=download_type['platform'], - product_title=product_title)) + for download_type in product["downloads"]: + if self._should_download_platform(download_type["platform"]) is False: + logger.info( + "Skipping {platform} for {product_title}".format( + platform=download_type["platform"], product_title=product_title + ) + ) continue product_folder = os.path.join( self.library_path, bundle_title, product_title ) # Create directory to save the files to - try: os.makedirs(product_folder) # noqa: E701 - except OSError: pass # noqa: E701 + try: + os.makedirs(product_folder) + except OSError: + pass # Download each file type of a product - for file_type in download_type['download_struct']: + for file_type in download_type["download_struct"]: try: - url = file_type['url']['web'] + url = file_type["url"]["web"] except KeyError: - logger.info("No url found: {bundle_title}/{product_title}" - .format(bundle_title=bundle_title, - product_title=product_title)) + logger.info( + "No url found: {bundle_title}/{product_title}".format( + bundle_title=bundle_title, product_title=product_title + ) + ) continue - url_filename = url.split('?')[0].split('/')[-1] - cache_file_key = order_id + ':' + url_filename - ext = url_filename.split('.')[-1] + url_filename = url.split("?")[0].split("/")[-1] + cache_file_key = order_id + ":" + url_filename + ext = url_filename.split(".")[-1] if self._should_download_file_type(ext) is False: - logger.info("Skipping the file {url_filename}" - .format(url_filename=url_filename)) + logger.info( + "Skipping the file {url_filename}".format( + url_filename=url_filename + ) + ) continue local_filename = os.path.join(product_folder, url_filename) @@ -277,23 +306,30 @@ class DownloadLibrary: # Check to see if the file still exists if product_r.status_code != 200: logger.debug( - "File missing for {bundle_title}/{product_title}: {url}" - .format(bundle_title=bundle_title, - product_title=product_title, - url=url)) + "File missing for {bundle_title}/{product_title}: {url}".format( + bundle_title=bundle_title, + product_title=product_title, + url=url, + ) + ) continue - logger.debug("Item request: {product_r}, Url: {url}" - .format(product_r=product_r, url=url)) + logger.debug( + "Item request: {product_r}, Url: {url}".format( + product_r=product_r, url=url + ) + ) file_info = { - 'url_last_modified': product_r.headers['Last-Modified'], + "url_last_modified": product_r.headers["Last-Modified"], } - if file_info['url_last_modified'] != cache_file_info.get('url_last_modified'): # noqa: E501 - if 'url_last_modified' in cache_file_info: + if file_info["url_last_modified"] != cache_file_info.get( + "url_last_modified" + ): + if "url_last_modified" in cache_file_info: last_modified = datetime.datetime.strptime( - cache_file_info['url_last_modified'], - '%a, %d %b %Y %H:%M:%S %Z' - ).strftime('%Y-%m-%d') + cache_file_info["url_last_modified"], + "%a, %d %b %Y %H:%M:%S %Z", + ).strftime("%Y-%m-%d") else: last_modified = None self._process_download( @@ -310,14 +346,17 @@ class DownloadLibrary: # quits it can keep track of the progress # Note: Only safe because of single thread, # need to change if refactor to multi threading - with open(self.cache_file, 'w') as outfile: + with open(self.cache_file, "w") as outfile: json.dump( - self.cache_data, outfile, - sort_keys=True, indent=4, + self.cache_data, + outfile, + sort_keys=True, + indent=4, ) - def _process_download(self, open_r, cache_file_key, file_info, - local_filename, rename_str=None): + def _process_download( + self, open_r, cache_file_key, file_info, local_filename, rename_str=None + ): try: if rename_str: self._rename_old_file(local_filename, rename_str) @@ -328,14 +367,19 @@ class DownloadLibrary: if self.progress_bar: # Do not overwrite the progress bar on next print print() - logger.error("Failed to download file {local_filename}" - .format(local_filename=local_filename)) + logger.error( + "Failed to download file {local_filename}".format( + local_filename=local_filename + ) + ) # Clean up broken downloaded file - try: os.remove(local_filename) # noqa: E701 - except OSError: pass # noqa: E701 + try: + os.remove(local_filename) + except OSError: + pass - if type(e).__name__ == 'KeyboardInterrupt': + if type(e).__name__ == "KeyboardInterrupt": sys.exit() else: @@ -349,11 +393,12 @@ class DownloadLibrary: open_r.connection.close() def _download_file(self, product_r, local_filename): - logger.info("Downloading: {local_filename}" - .format(local_filename=local_filename)) + logger.info( + "Downloading: {local_filename}".format(local_filename=local_filename) + ) - with open(local_filename, 'wb') as outfile: - total_length = product_r.headers.get('content-length') + with open(local_filename, "wb") as outfile: + total_length = product_r.headers.get("content-length") if total_length is None: # no content length header outfile.write(product_r.content) else: @@ -365,18 +410,21 @@ class DownloadLibrary: pb_width = 50 done = int(pb_width * dl / total_length) if self.progress_bar: - print("\t{percent}% [{filler}{space}]" - .format(percent=int(done * (100 / pb_width)), - filler='=' * done, - space=' ' * (pb_width - done), - ), end='\r') + print( + "\t{percent}% [{filler}{space}]".format( + percent=int(done * (100 / pb_width)), + filler="=" * done, + space=" " * (pb_width - done), + ), + end="\r", + ) if dl != total_length: raise ValueError("Download did not complete") def _load_cache_data(self, cache_file): try: - with open(cache_file, 'r') as f: + with open(cache_file, "r") as f: cache_data = json.load(f) except FileNotFoundError: cache_data = {} @@ -385,18 +433,20 @@ class DownloadLibrary: def _get_purchase_keys(self): try: - library_r = self.session.get('https://www.humblebundle.com/home/library') # noqa: E501 + library_r = self.session.get("https://www.humblebundle.com/home/library") except Exception: logger.exception("Failed to get list of purchases") return [] logger.debug("Library request: " + str(library_r)) library_page = parsel.Selector(text=library_r.text) - user_data = library_page.css('#user-home-json-data').xpath('string()').extract_first() # noqa: E501 + user_data = ( + library_page.css("#user-home-json-data").xpath("string()").extract_first() + ) if user_data is None: raise Exception("Unable to download user-data, cookies missing?") orders_json = json.loads(user_data) - return orders_json['gamekeys'] + return orders_json["gamekeys"] def _should_download_platform(self, platform): platform = platform.lower() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..91efccd --- /dev/null +++ b/poetry.lock @@ -0,0 +1,469 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +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"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +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"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cssselect" +version = "1.2.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, + {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "lxml" +version = "5.2.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.10)"] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "parsel" +version = "1.9.1" +description = "Parsel is a library to extract data from HTML and XML using XPath and CSS selectors" +optional = false +python-versions = ">=3.8" +files = [ + {file = "parsel-1.9.1-py2.py3-none-any.whl", hash = "sha256:c4a777ee6c3ff5e39652b58e351c5cf02c12ff420d05b07a7966aebb68ab1700"}, + {file = "parsel-1.9.1.tar.gz", hash = "sha256:14e00dc07731c9030db620c195fcae884b5b4848e9f9c523c6119f708ccfa9ac"}, +] + +[package.dependencies] +cssselect = ">=1.2.0" +jmespath = "*" +lxml = "*" +packaging = "*" +w3lib = ">=1.19.0" + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.2" +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"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +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"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "w3lib" +version = "2.2.1" +description = "Library of web-related functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "w3lib-2.2.1-py3-none-any.whl", hash = "sha256:e56d81c6a6bf507d7039e0c95745ab80abd24b465eb0f248af81e3eaa46eb510"}, + {file = "w3lib-2.2.1.tar.gz", hash = "sha256:756ff2d94c64e41c8d7c0c59fea12a5d0bc55e33a531c7988b4a163deb9b07dd"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "67a578afce6d570192e284e76d3745083c4793ad80dffe69a414addd9fedcdce" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cfff64e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "humblebundle-downloader" +version = "0.4.1" +description = "Download your Humble Bundle library" +authors = ["Eddy Hintze "] +license = "MIT" +readme = "README.md" + +[tool.poetry.urls] +homepage = "https://github.com/xtream1101/humblebundle-downloader" +repository = "https://github.com/xtream1101/humblebundle-downloader" + +[tool.poetry.scripts] +hbd = "humblebundle_downloader.cli:cli" + +[tool.poetry.dependencies] +python = "^3.10" +requests = "^2.32.3" +parsel = "^1.9.1" + +[tool.poetry.group.test.dependencies] +pytest = "^8.3.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.py b/setup.py deleted file mode 100644 index 7fcd95b..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -from setuptools import setup -from humblebundle_downloader._version import __version__ - - -with open('README.md', 'r') as f: - long_description = f.read() - -setup( - name='humblebundle-downloader', - packages=['humblebundle_downloader'], - version=__version__, - description='Download your Humble Bundle library', - long_description=long_description, - long_description_content_type='text/markdown', - author='Eddy Hintze', - author_email="eddy@hintze.co", - url="https://github.com/xtream1101/humblebundle-downloader", - license='MIT', - classifiers=[ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", - ], - entry_points={ - 'console_scripts': [ - 'hbd=humblebundle_downloader.cli:cli', - ], - }, - python_requires='~=3.4', - install_requires=[ - 'requests', - 'parsel', - ], - -) diff --git a/tests/test_cli.py b/tests/test_cli.py index bc838d7..bbf1a96 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,13 +4,13 @@ from humblebundle_downloader.cli import parse_args def test_old_action_format(): with pytest.raises(DeprecationWarning): - _ = parse_args(['download', '-l', 'some_path', '-c', 'fake_cookie']) + _ = parse_args(["download", "-l", "some_path", "-c", "fake_cookie"]) def test_no_action(): - args = parse_args(['-l', 'some_path', '-c', 'fake_cookie']) - assert args.library_path == 'some_path' - assert args.cookie_file == 'fake_cookie' + args = parse_args(["-l", "some_path", "-c", "fake_cookie"]) + assert args.library_path == "some_path" + assert args.cookie_file == "fake_cookie" def test_no_args(): diff --git a/tests/test_download_library.py b/tests/test_download_library.py index f3d473d..6ee2ea6 100644 --- a/tests/test_download_library.py +++ b/tests/test_download_library.py @@ -6,46 +6,46 @@ from humblebundle_downloader.download_library import DownloadLibrary ### def test_include_logic_has_values(): dl = DownloadLibrary( - 'fake_library_path', - ext_include=['pdf', 'EPub'], + "fake_library_path", + ext_include=["pdf", "EPub"], ) - assert dl._should_download_file_type('pdf') is True - assert dl._should_download_file_type('df') is False - assert dl._should_download_file_type('ePub') is True - assert dl._should_download_file_type('mobi') is False + assert dl._should_download_file_type("pdf") is True + assert dl._should_download_file_type("df") is False + assert dl._should_download_file_type("ePub") is True + assert dl._should_download_file_type("mobi") is False def test_include_logic_empty(): dl = DownloadLibrary( - 'fake_library_path', + "fake_library_path", ext_include=[], ) - assert dl._should_download_file_type('pdf') is True - assert dl._should_download_file_type('df') is True - assert dl._should_download_file_type('EPub') is True - assert dl._should_download_file_type('mobi') is True + assert dl._should_download_file_type("pdf") is True + assert dl._should_download_file_type("df") is True + assert dl._should_download_file_type("EPub") is True + assert dl._should_download_file_type("mobi") is True def test_exclude_logic_has_values(): dl = DownloadLibrary( - 'fake_library_path', - ext_exclude=['pdf', 'EPub'], + "fake_library_path", + ext_exclude=["pdf", "EPub"], ) - assert dl._should_download_file_type('pdf') is False - assert dl._should_download_file_type('df') is True - assert dl._should_download_file_type('ePub') is False - assert dl._should_download_file_type('mobi') is True + assert dl._should_download_file_type("pdf") is False + assert dl._should_download_file_type("df") is True + assert dl._should_download_file_type("ePub") is False + assert dl._should_download_file_type("mobi") is True def test_exclude_logic_empty(): dl = DownloadLibrary( - 'fake_library_path', + "fake_library_path", ext_exclude=[], ) - assert dl._should_download_file_type('pdf') is True - assert dl._should_download_file_type('df') is True - assert dl._should_download_file_type('EPub') is True - assert dl._should_download_file_type('mobi') is True + assert dl._should_download_file_type("pdf") is True + assert dl._should_download_file_type("df") is True + assert dl._should_download_file_type("EPub") is True + assert dl._should_download_file_type("mobi") is True ### @@ -53,26 +53,26 @@ def test_exclude_logic_empty(): ### def test_download_platform_filter_none(): dl = DownloadLibrary( - 'fake_library_path', + "fake_library_path", platform_include=None, ) - assert dl._should_download_platform('ebook') is True - assert dl._should_download_platform('audio') is True + assert dl._should_download_platform("ebook") is True + assert dl._should_download_platform("audio") is True def test_download_platform_filter_blank(): dl = DownloadLibrary( - 'fake_library_path', + "fake_library_path", platform_include=[], ) - assert dl._should_download_platform('ebook') is True - assert dl._should_download_platform('audio') is True + assert dl._should_download_platform("ebook") is True + assert dl._should_download_platform("audio") is True def test_download_platform_filter_audio(): dl = DownloadLibrary( - 'fake_library_path', - platform_include=['audio'], + "fake_library_path", + platform_include=["audio"], ) - assert dl._should_download_platform('ebook') is False - assert dl._should_download_platform('audio') is True + assert dl._should_download_platform("ebook") is False + assert dl._should_download_platform("audio") is True