diff --git a/.flake8 b/.flake8 index 7da1f96..ad98539 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] max-line-length = 100 +extend-ignore = E203 diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index af5f09c..3a29063 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -36,3 +36,5 @@ jobs: - name: Lint package docstrings and comments with pydocstyle if: success() || failure() run: poetry run pydocstyle falcon_toolkit/ + - name: Lint package with black + run: poetry run black -l 100 --check falcon_toolkit/ diff --git a/falcon_toolkit/__init__.py b/falcon_toolkit/__init__.py index 3562050..499c7af 100644 --- a/falcon_toolkit/__init__.py +++ b/falcon_toolkit/__init__.py @@ -2,8 +2,9 @@ A CLI for CrowdStrike Falcon, made with love by the CrowdStrike Services Team. """ + __all__ = [ - '__version__', + "__version__", ] from falcon_toolkit.common.meta import __version__ diff --git a/falcon_toolkit/common/auth.py b/falcon_toolkit/common/auth.py index 5926152..228a2f6 100644 --- a/falcon_toolkit/common/auth.py +++ b/falcon_toolkit/common/auth.py @@ -5,6 +5,7 @@ hold a FalconPy authentication object. It will be possible to generate more of these backends for other purposes, such as for authentication against alternative API gateways. """ + from __future__ import annotations from abc import ( abstractmethod, diff --git a/falcon_toolkit/common/auth_backends/default.py b/falcon_toolkit/common/auth_backends/default.py index b47d334..1db1deb 100644 --- a/falcon_toolkit/common/auth_backends/default.py +++ b/falcon_toolkit/common/auth_backends/default.py @@ -2,6 +2,7 @@ This file configures the default authentication backends. """ + from falcon_toolkit.common.auth_backends.public_mssp import PublicCloudFlightControlParentCIDBackend from falcon_toolkit.common.auth_backends.public_single_cid import PublicCloudSingleCIDBackend diff --git a/falcon_toolkit/common/auth_backends/public_mssp.py b/falcon_toolkit/common/auth_backends/public_mssp.py index b8633ec..6b9da1e 100644 --- a/falcon_toolkit/common/auth_backends/public_mssp.py +++ b/falcon_toolkit/common/auth_backends/public_mssp.py @@ -3,6 +3,7 @@ This authentication backend can take public cloud API keys (US-1, US-2, EU-1), and will ask the user which child CID to authenticate against. """ + import os from typing import Dict, Optional @@ -93,10 +94,10 @@ def dump_config(self) -> Dict[str, object]: keyring. """ config: Dict[str, object] = {} - config['client_id']: str = self.client_id - config['cloud_name']: str = self.cloud_name - config['ssl_verify']: bool = self.ssl_verify - config['proxy']: Dict[str, str] = self.proxy + config["client_id"]: str = self.client_id + config["cloud_name"]: str = self.cloud_name + config["ssl_verify"]: bool = self.ssl_verify + config["proxy"]: Dict[str, str] = self.proxy return config @@ -112,18 +113,15 @@ def authenticate(self) -> Client: child_cids = parent_client.flight_control.get_child_cids() chosen_cid_str = os.environ.get("FALCON_MSSP_CHILD_CID") if chosen_cid_str and chosen_cid_str.lower() in child_cids: - chosen_cid = parent_client.flight_control.get_child_cid_data( - cids=[chosen_cid_str] - )[chosen_cid_str] + chosen_cid = parent_client.flight_control.get_child_cid_data(cids=[chosen_cid_str])[ + chosen_cid_str + ] else: child_cids_data = parent_client.flight_control.get_child_cid_data(cids=child_cids) - chosen_cid_str = choose_cid( - cids=child_cids_data, - prompt_text="MSSP Child CID Search" - ) + chosen_cid_str = choose_cid(cids=child_cids_data, prompt_text="MSSP Child CID Search") chosen_cid = child_cids_data[chosen_cid_str] - chosen_cid_name = chosen_cid['name'] + chosen_cid_name = chosen_cid["name"] print(f"Connecting to {chosen_cid_name}") client = Client( diff --git a/falcon_toolkit/common/auth_backends/public_single_cid.py b/falcon_toolkit/common/auth_backends/public_single_cid.py index a909a67..665507c 100644 --- a/falcon_toolkit/common/auth_backends/public_single_cid.py +++ b/falcon_toolkit/common/auth_backends/public_single_cid.py @@ -3,6 +3,7 @@ This authentication backend can take public cloud API keys (US-1, US-2, EU-1), and will return an OAuth2 object suitable for authenticating with FalconPy. """ + from typing import Dict, Optional import keyring @@ -81,10 +82,10 @@ def dump_config(self) -> Dict[str, object]: keyring. """ config: Dict[str, object] = {} - config['client_id'] = self.client_id - config['cloud_name'] = self.cloud_name - config['ssl_verify'] = self.ssl_verify - config['proxy'] = self.proxy + config["client_id"] = self.client_id + config["cloud_name"] = self.cloud_name + config["ssl_verify"] = self.ssl_verify + config["proxy"] = self.proxy return config diff --git a/falcon_toolkit/common/auth_backends/utils.py b/falcon_toolkit/common/auth_backends/utils.py index 91bdaab..0a79fcc 100644 --- a/falcon_toolkit/common/auth_backends/utils.py +++ b/falcon_toolkit/common/auth_backends/utils.py @@ -8,6 +8,7 @@ - A cloud selection function to allow a user to choose a cloud via Prompt Toolkit - Advanced options configuration for overriding cloud, TLS validation, etc. """ + from typing import ( Dict, List, @@ -56,8 +57,8 @@ class AdvancedOptionsType(NamedTuple): def advanced_options_wizard() -> AdvancedOptionsType: """Define advanced connection options and return an AdvancedOptionsType.""" advanced_options_input = fancy_input("Do you want to configure more options? [y/n]: ") - if advanced_options_input not in ('y', 'Y'): - return AdvancedOptionsType('auto', True, None) + if advanced_options_input not in ("y", "Y"): + return AdvancedOptionsType("auto", True, None) cloud_name = cloud_choice() @@ -76,7 +77,7 @@ def advanced_options_wizard() -> AdvancedOptionsType: proxy_url_input = fancy_input("HTTPS proxy URL (leave blank if not needed): ", loop=False) if proxy_url_input: - proxy_dict = {'https', proxy_url_input} + proxy_dict = {"https", proxy_url_input} advanced_options_result = AdvancedOptionsType(cloud_name, ssl_verify, proxy_dict) return advanced_options_result diff --git a/falcon_toolkit/common/cli.py b/falcon_toolkit/common/cli.py index 3e989d8..42642b1 100644 --- a/falcon_toolkit/common/cli.py +++ b/falcon_toolkit/common/cli.py @@ -1,4 +1,5 @@ """Falcon Toolkit: Common CLI Functions.""" + import sys from typing import List @@ -13,25 +14,27 @@ def get_instance(ctx: click.Context): """Load a specified Falcon instance configuration ready for use.""" - config: FalconToolkitConfig = ctx.obj['config'] - profile_name: str = ctx.obj['profile_name'] + config: FalconToolkitConfig = ctx.obj["config"] + profile_name: str = ctx.obj["profile_name"] if profile_name in config.instances: profile = config.instances[profile_name] elif not profile_name and len(config.instances) == 1: profile = list(config.instances.values())[0] elif not config.instances: - click.echo(click.style("No profiles are configured. Please set one up first.", fg='red')) + click.echo(click.style("No profiles are configured. Please set one up first.", fg="red")) sys.exit(1) elif not profile_name: - click.echo(click.style( - "Multiple profiles are configured, so you must use the -p/--profile option " - "to choose a profile to execute this tool with.", - fg='red', - )) + click.echo( + click.style( + "Multiple profiles are configured, so you must use the -p/--profile option " + "to choose a profile to execute this tool with.", + fg="red", + ) + ) sys.exit(1) else: - click.echo(click.style(f"The profile {profile_name} does not exist.", fg='red')) + click.echo(click.style(f"The profile {profile_name} does not exist.", fg="red")) sys.exit(1) return profile @@ -39,13 +42,13 @@ def get_instance(ctx: click.Context): def parse_cli_filters(filter_kv_strings: List[str], client: Client) -> FalconFilter: """Parse consecutive chains of -f filters into a FalconFilter object for later use.""" - filters = client.FalconFilter(dialect='hosts') + filters = client.FalconFilter(dialect="hosts") for filter_kv_string in filter_kv_strings: - if '=' not in filter_kv_string: + if "=" not in filter_kv_string: raise ValueError("Filter key=value string is in the wrong format") first_equals = filter_kv_string.index("=") filter_key = filter_kv_string[0:first_equals] - filter_value = filter_kv_string[first_equals + 1:] + filter_value = filter_kv_string[first_equals + 1 :] filters.create_new_filter_from_kv_string(filter_key, filter_value) return filters diff --git a/falcon_toolkit/common/config.py b/falcon_toolkit/common/config.py index a42deab..40e34fb 100644 --- a/falcon_toolkit/common/config.py +++ b/falcon_toolkit/common/config.py @@ -4,6 +4,7 @@ These configurations are stored in a wider config file stored within the configuration path (usually ~/FalconToolkit/FalconToolkit.json). """ + from __future__ import annotations import importlib.util import json @@ -63,9 +64,7 @@ def load_config(self, config: Dict[str, object]): break if not matching_auth_backend: - raise ValueError( - f"Auth backend {auth_backend_name} is not loaded or does not exist" - ) + raise ValueError(f"Auth backend {auth_backend_name} is not loaded or does not exist") auth_backend_config = auth.get("backend_config") if not auth_backend_config: @@ -81,7 +80,7 @@ def dump_config(self): "auth": { "backend_name": self.auth_backend.simple_name, "backend_config": self.auth_backend.dump_config(), - } + }, } @@ -113,7 +112,7 @@ def __init__(self, config_path: str): """Load a Falcon Toolkit configuration file from disk.""" self.config_file_path = os.path.join(config_path, CONFIG_FILENAME) if os.path.exists(self.config_file_path): - with open(self.config_file_path, 'rb') as config_file_handle: + with open(self.config_file_path, "rb") as config_file_handle: config_data = json.load(config_file_handle) else: config_data = {} @@ -139,7 +138,7 @@ def write_config(self): "auth_backends": self.additional_auth_backend_paths, "instances": [x.dump_config() for x in list(self.instances.values())], } - with open(self.config_file_path, 'w', encoding='utf8') as config_file_handle: + with open(self.config_file_path, "w", encoding="utf8") as config_file_handle: json.dump(config_data, config_file_handle, sort_keys=True, indent=4) def add_instance(self): diff --git a/falcon_toolkit/common/console_utils.py b/falcon_toolkit/common/console_utils.py index 5fd0417..155afb5 100644 --- a/falcon_toolkit/common/console_utils.py +++ b/falcon_toolkit/common/console_utils.py @@ -3,12 +3,13 @@ This file contains tools to help with displaying data on the console in a user-friendly, colourful and/or helpful way. """ + import platform -ESC = '\033' -OSC = ESC + ']' -ST = ESC + '\\' +ESC = "\033" +OSC = ESC + "]" +ST = ESC + "\\" def build_hyperlink(target: str, text: str, link_id: str = None): @@ -17,7 +18,7 @@ def build_hyperlink(target: str, text: str, link_id: str = None): id_str = "id=" + link_id else: id_str = "" - return f'{OSC}8;{id_str};{target}{ST}{text}{OSC}8;;{ST}' + return f"{OSC}8;{id_str};{target}{ST}{text}{OSC}8;;{ST}" def build_file_hyperlink(file_path: str, text: str, link_id: str = None): @@ -27,7 +28,7 @@ def build_file_hyperlink(file_path: str, text: str, link_id: str = None): else: hostname = platform.node() - if file_path.startswith('/'): + if file_path.startswith("/"): file_path = file_path[1:] uri_file_path = f"file://{hostname}/{file_path}" diff --git a/falcon_toolkit/common/constants.py b/falcon_toolkit/common/constants.py index 808e76b..326472c 100644 --- a/falcon_toolkit/common/constants.py +++ b/falcon_toolkit/common/constants.py @@ -1,4 +1,5 @@ """Falcon Toolkit: Common Constants.""" + import os import platformdirs @@ -16,6 +17,6 @@ # Logging LOG_CONSOLE_FORMATTER = "%(message)s" -LOG_FILE_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S%z' -LOG_FILE_OUTPUT_FORMAT = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s' +LOG_FILE_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z" +LOG_FILE_OUTPUT_FORMAT = "%(asctime)s %(name)-24s %(levelname)-8s %(message)s" LOG_SUB_DIR = "logs" diff --git a/falcon_toolkit/common/logging_config.py b/falcon_toolkit/common/logging_config.py index c9c5413..1af2593 100644 --- a/falcon_toolkit/common/logging_config.py +++ b/falcon_toolkit/common/logging_config.py @@ -4,6 +4,7 @@ capture logs from both the toolkit itself, as well as Caracara behind the scenes. All logs are compressed using gzip to keep file sizes down. """ + import datetime import gzip import logging @@ -48,7 +49,7 @@ def configure_logger( # Gzip compress the main log file on the fly via a memory stream. # This is required to avoid the gzip file from being corrupted if the # the script ends prematurely (e.g, it crashes). - log_output_file = gzip.open(log_filepath, mode='wt', encoding='utf-8') + log_output_file = gzip.open(log_filepath, mode="wt", encoding="utf-8") stream_handler = logging.StreamHandler(log_output_file) memory_handler = logging.handlers.MemoryHandler( capacity=1024, @@ -60,7 +61,7 @@ def configure_logger( datefmt=LOG_FILE_DATE_FORMAT, format=LOG_FILE_OUTPUT_FORMAT, handlers=[memory_handler], - encoding='utf-8', + encoding="utf-8", ) else: logging.basicConfig( @@ -68,14 +69,14 @@ def configure_logger( format=LOG_FILE_OUTPUT_FORMAT, datefmt=LOG_FILE_DATE_FORMAT, filename=log_filepath, - filemode='wt', + filemode="wt", ) console = logging.StreamHandler() console.setLevel(level=log_level) console_formatter = logging.Formatter(LOG_CONSOLE_FORMATTER) console.setFormatter(console_formatter) - logging.getLogger('').addHandler(console) + logging.getLogger("").addHandler(console) logging.debug("Logger configured") return log_filepath_base diff --git a/falcon_toolkit/common/meta.py b/falcon_toolkit/common/meta.py index 010d568..87d326d 100644 --- a/falcon_toolkit/common/meta.py +++ b/falcon_toolkit/common/meta.py @@ -6,6 +6,4 @@ import pkg_resources # Derive the version via pkg_resources, which is populated based on the version in pyproject.toml -__version__ = pkg_resources.get_distribution( - __name__.split(".", maxsplit=1)[0] -).version +__version__ = pkg_resources.get_distribution(__name__.split(".", maxsplit=1)[0]).version diff --git a/falcon_toolkit/common/namespace.py b/falcon_toolkit/common/namespace.py index a0bc8f2..06560d1 100644 --- a/falcon_toolkit/common/namespace.py +++ b/falcon_toolkit/common/namespace.py @@ -38,9 +38,7 @@ def __getitem__(self, key): def __setitem__(self, key, value): """Assign a key to a value in the namespace.""" - self.__init__( - ** {key: value} - ) + self.__init__(**{key: value}) def __delitem__(self, key): """Delete an item from the namespace.""" diff --git a/falcon_toolkit/common/utils.py b/falcon_toolkit/common/utils.py index 11f0a38..ed36351 100644 --- a/falcon_toolkit/common/utils.py +++ b/falcon_toolkit/common/utils.py @@ -3,6 +3,7 @@ This file is a catch-all for small code snippets that can be shared across the various sub-modules of the application. """ + import os from typing import Dict, Iterable @@ -21,8 +22,7 @@ def fancy_input(prompt_text: str, loop: bool = True): """Request user input (with colour). Optionally loop until the input is not blank.""" inputted = False - colour_prompt = Style.BRIGHT + Fore.BLUE + \ - prompt_text + Fore.RESET + Style.RESET_ALL + colour_prompt = Style.BRIGHT + Fore.BLUE + prompt_text + Fore.RESET + Style.RESET_ALL while not inputted: data = input(colour_prompt) @@ -62,11 +62,11 @@ def filename_safe_string(unsafe_string: str) -> str: This function is heavily inspired by https://stackoverflow.com/a/7406369. """ safe_string = "".join( - [c for c in unsafe_string if c.isalpha() or c.isdigit() or c == ' '] + [c for c in unsafe_string if c.isalpha() or c.isdigit() or c == " "] ).rstrip() # Replace spaces with underscores to match the general format of the log filename - clean_string = safe_string.replace(' ', '_') + clean_string = safe_string.replace(" ", "_") return clean_string diff --git a/falcon_toolkit/containment/cli.py b/falcon_toolkit/containment/cli.py index 08e6a2e..03174bf 100644 --- a/falcon_toolkit/containment/cli.py +++ b/falcon_toolkit/containment/cli.py @@ -2,6 +2,7 @@ This file contains the CLI options for the falcon containment commands. """ + import logging import sys @@ -23,26 +24,26 @@ @click.group( - name='containment', + name="containment", help="Manage the containment status of systems in a CID", ) @click.pass_context @optgroup.group( "Specify devices", cls=MutuallyExclusiveOptionGroup, - help="Choose no more than one method to choose systems to contain or uncontain" + help="Choose no more than one method to choose systems to contain or uncontain", ) @optgroup.option( - '-d', - '--device-id-list', - 'device_id_list', + "-d", + "--device-id-list", + "device_id_list", type=click.STRING, - help="Specify a list of Device IDs (AIDs), comma delimited" + help="Specify a list of Device IDs (AIDs), comma delimited", ) @optgroup.option( - '-df', - '--device-id-file', - 'device_id_file', + "-df", + "--device-id-file", + "device_id_file", type=click.STRING, help=( "Specify a list of Device IDs (AIDs) in an external file, one per line; " @@ -50,9 +51,9 @@ ), ) @optgroup.option( - '-f', - '--filter', - 'filter_kv_string', + "-f", + "--filter", + "filter_kv_string", type=click.STRING, multiple=True, help="Filter hosts based on standard Falcon filters", @@ -66,20 +67,21 @@ def cli_containment( """Manage the containment status of hosts in Falcon.""" instance = get_instance(ctx) client: Client = instance.auth_backend.authenticate() - ctx.obj['client'] = client + ctx.obj["client"] = client device_ids = None params = True if filter_kv_string: - click.echo(click.style( - "Managing all hosts that match the provided Falcon filters", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Managing all hosts that match the provided Falcon filters", + fg="magenta", + bold=True, + ) + ) logging.info( - "Managing the containment status of all hosts that match the " - "provided Falcon filters" + "Managing the containment status of all hosts that match the provided Falcon filters" ) filters = parse_cli_filters(filter_kv_string, client).get_fql() @@ -90,11 +92,13 @@ def cli_containment( device_ids = client.hosts.get_device_ids(filters=filters) elif device_id_list: - click.echo(click.style( - "Managing the devices identified by the IDs provided on the command line", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Managing the devices identified by the IDs provided on the command line", + fg="magenta", + bold=True, + ) + ) logging.info("Managing the devices identified by the IDs provided on the command line") device_ids = set() @@ -104,16 +108,18 @@ def cli_containment( device_ids.add(device_id) elif device_id_file: - click.echo(click.style( - "Managing the devices identified by the IDs listed in a file", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Managing the devices identified by the IDs listed in a file", + fg="magenta", + bold=True, + ) + ) click.echo(click.style("File path: ", bold=True), nl=False) click.echo(device_id_file) logging.info("Managing the devices identified by the IDs listed in %s", device_id_file) - with open(device_id_file, 'rt', encoding='ascii') as device_id_file_handle: + with open(device_id_file, "rt", encoding="ascii") as device_id_file_handle: device_ids = set() for line in device_id_file_handle: line = line.strip() @@ -123,14 +129,16 @@ def cli_containment( params = False if params and device_ids is None: - click.echo(click.style( - "No devices matched the provided filters", - fg='red', - bold=True, - )) + click.echo( + click.style( + "No devices matched the provided filters", + fg="red", + bold=True, + ) + ) sys.exit(1) - ctx.obj['device_ids'] = device_ids + ctx.obj["device_ids"] = device_ids logging.debug(device_ids) @@ -145,13 +153,15 @@ def check_empty_device_ids(client) -> List[str]: This therefore shifts the user logic to after the group parameters have been evaluated to improve the user experience and avoid confusion. """ - click.echo(click.style( - "You did not specify any parameters. This command will manage the containment " - "status of ALL devices in the Falcon tenant!", - fg='yellow', - )) + click.echo( + click.style( + "You did not specify any parameters. This command will manage the containment " + "status of ALL devices in the Falcon tenant!", + fg="yellow", + ) + ) - click.echo("You must enter the string \"I AM SURE!\" to proceed.") + click.echo('You must enter the string "I AM SURE!" to proceed.') confirmation = input("Are you sure? ") if confirmation != "I AM SURE!": print("You did not confirm you were sure. Aborting!") @@ -163,14 +173,14 @@ def check_empty_device_ids(client) -> List[str]: @cli_containment.command( - name='contain', - help='Network contain systems in a Falcon tenant', + name="contain", + help="Network contain systems in a Falcon tenant", ) @click.pass_context def contain(ctx: click.Context): """Network contain systems.""" - client: Client = ctx.obj['client'] - device_ids: List[str] = ctx.obj['device_ids'] + client: Client = ctx.obj["client"] + device_ids: List[str] = ctx.obj["device_ids"] if device_ids is None: device_ids = check_empty_device_ids(client) @@ -185,14 +195,14 @@ def contain(ctx: click.Context): @cli_containment.command( - name='uncontain', - help='Lift network containment from systems in a Falcon tenant', + name="uncontain", + help="Lift network containment from systems in a Falcon tenant", ) @click.pass_context def uncontain(ctx: click.Context): """Lift network containment on systems.""" - client: Client = ctx.obj['client'] - device_ids: List[str] = ctx.obj['device_ids'] + client: Client = ctx.obj["client"] + device_ids: List[str] = ctx.obj["device_ids"] if device_ids is None: device_ids = check_empty_device_ids(client) diff --git a/falcon_toolkit/containment/perform_containment.py b/falcon_toolkit/containment/perform_containment.py index 186630d..5f61559 100644 --- a/falcon_toolkit/containment/perform_containment.py +++ b/falcon_toolkit/containment/perform_containment.py @@ -2,6 +2,7 @@ This file contains the logic required to contain and uncontain systems. """ + import logging from operator import itemgetter @@ -24,8 +25,8 @@ def result_output( return header_row = [ - click.style("Device ID", bold=True, fg='blue'), - click.style("Result", bold=True, fg='blue'), + click.style("Device ID", bold=True, fg="blue"), + click.style("Result", bold=True, fg="blue"), ] results_table = [header_row] @@ -33,10 +34,12 @@ def result_output( if resources: success_rows = [] for resource in resources: - success_rows.append([ - resource['id'], - click.style("Success", fg='green'), - ]) + success_rows.append( + [ + resource["id"], + click.style("Success", fg="green"), + ] + ) success_rows = sorted(success_rows, key=itemgetter(1, 0)) results_table.extend(success_rows) @@ -44,32 +47,36 @@ def result_output( if errors: error_rows = [] for error in errors: - code = error['code'] - message = error['message'] + code = error["code"] + message = error["message"] if message.startswith("Device "): device_id = message.split(" ")[1] else: device_id = "Unknown" - result_text = click.style("Failed", fg='red') + result_text = click.style("Failed", fg="red") if code == 409: result_text += ": incompatible current containment state" else: result_text += f": {message} (code: {code})" - error_rows.append([ - device_id, - result_text, - ]) + error_rows.append( + [ + device_id, + result_text, + ] + ) error_rows = sorted(error_rows, key=itemgetter(1, 0)) results_table.extend(error_rows) - click.echo(tabulate.tabulate( - results_table, - tablefmt='fancy_grid', - )) + click.echo( + tabulate.tabulate( + results_table, + tablefmt="fancy_grid", + ) + ) def guard_rail_confirmation(device_count: int, action: str) -> bool: @@ -86,8 +93,7 @@ def guard_rail_confirmation(device_count: int, action: str) -> bool: (True, f"Release {device_count} devices from network containment"), ] prompt_text = ( - f"Are you sure you want to release {device_count} devices " - "from network containment?" + f"Are you sure you want to release {device_count} devices from network containment?" ) confirmation: bool = csradiolist_dialog( @@ -97,10 +103,10 @@ def guard_rail_confirmation(device_count: int, action: str) -> bool: ).run() if confirmation: - click.echo(click.style("User confirmed action", bold=True, fg='green')) + click.echo(click.style("User confirmed action", bold=True, fg="green")) return True - click.echo(click.style("Aborted!", bold=True, fg='red')) + click.echo(click.style("Aborted!", bold=True, fg="red")) return False @@ -128,22 +134,22 @@ def perform_containment_action( response = client.hosts.hosts_api.perform_action( action_name=action, - ids=device_ids[i: i + limit], + ids=device_ids[i : i + limit], ) logging.debug(response) - if response['status_code'] == 202: - click.echo(click.style("Succeeded", fg='green')) - elif 'resources' in response['body'] and response['body']['resources']: - click.echo(click.style("Partially succeeded", fg='yellow')) + if response["status_code"] == 202: + click.echo(click.style("Succeeded", fg="green")) + elif "resources" in response["body"] and response["body"]["resources"]: + click.echo(click.style("Partially succeeded", fg="yellow")) else: - click.echo(click.style("Failed", fg='red')) + click.echo(click.style("Failed", fg="red")) - batch_resources = response['body'].get("resources") + batch_resources = response["body"].get("resources") if batch_resources: resources.extend(batch_resources) - batch_errors = response['body'].get("errors") + batch_errors = response["body"].get("errors") if batch_errors: errors.extend(batch_errors) diff --git a/falcon_toolkit/falcon.py b/falcon_toolkit/falcon.py index ce666bc..5dd6420 100755 --- a/falcon_toolkit/falcon.py +++ b/falcon_toolkit/falcon.py @@ -59,26 +59,26 @@ @click.group() @click.pass_context @click.option( - '-c', - '--config-path', - envvar='FALCON_TOOLKIT_CONFIG_DIR', + "-c", + "--config-path", + envvar="FALCON_TOOLKIT_CONFIG_DIR", type=click.STRING, default=DEFAULT_CONFIG_DIR, help="Path to the configuration directory (default: %userappdata%/FalconToolkit/)", ) @click.option( - '-v', - '--verbose', - envvar='FALCON_TOOLKIT_VERBOSE', + "-v", + "--verbose", + envvar="FALCON_TOOLKIT_VERBOSE", type=click.BOOL, is_flag=True, default=False, help="Enable info-level logging at the CLI", ) @click.option( - '-p', - '--profile', - envvar='FALCON_TOOLKIT_PROFILE', + "-p", + "--profile", + envvar="FALCON_TOOLKIT_PROFILE", type=click.STRING, default=None, help=( @@ -123,18 +123,18 @@ def cli( # Configure context that can be passed down to other options ctx.ensure_object(dict) - click.echo(click.style("Falcon Toolkit", fg='blue', bold=True)) + click.echo(click.style("Falcon Toolkit", fg="blue", bold=True)) hyperlink = build_file_hyperlink(config_path, config_path, "falcon_config_path") - click.echo(click.style(f"Configuration Directory: {hyperlink}", fg='black')) + click.echo(click.style(f"Configuration Directory: {hyperlink}", fg="black")) if verbose: log_level = logging.INFO else: log_level = logging.CRITICAL if ( - config_path == DEFAULT_CONFIG_DIR and - os.path.exists(OLD_DEFAULT_CONFIG_DIR) and - not os.path.exists(config_path) + config_path == DEFAULT_CONFIG_DIR + and os.path.exists(OLD_DEFAULT_CONFIG_DIR) + and not os.path.exists(config_path) ): # The user has used Falcon Toolkit before, and uses the default directory, so we # offer to move the configuration folder for them. @@ -151,7 +151,7 @@ def cli( ( "COPY_CONFIG_ONLY", f"create a new one at {config_path}, and copy my configuration file there", - ) + ), ] choice = csradiolist_dialog( title="Falcon Toolkit Configuration Directory", @@ -192,7 +192,7 @@ def cli( # Configure and load the configuration object configure_data_dir(config_path) config = FalconToolkitConfig(config_path=config_path) - ctx.obj['config'] = config + ctx.obj["config"] = config # Configure the logger log_path = os.path.join(config_path, LOG_SUB_DIR) @@ -202,10 +202,10 @@ def cli( log_level=log_level, log_compression=True, ) - ctx.obj['log_filename_base'] = log_filename_base + ctx.obj["log_filename_base"] = log_filename_base # Pass a profile name down the chain in case one is selected - ctx.obj['profile_name'] = profile + ctx.obj["profile_name"] = profile @cli.result_callback() @@ -222,19 +222,19 @@ def cli_process_result( # pylint: disable=unused-argument @cli.group( - help='Show, create and delete Falcon Toolkit connection profiles', + help="Show, create and delete Falcon Toolkit connection profiles", ) def profiles(): """Root command group to handle connection profiles.""" @profiles.command( - name='delete', + name="delete", help="Delete a Falcon connection profile.", ) @click.pass_context @click.argument( - 'profile_name', + "profile_name", type=click.STRING, ) def profiles_delete( @@ -243,40 +243,40 @@ def profiles_delete( ): """Delete a connection profile.""" click.echo(f"Deleting {profile_name}") - config: FalconToolkitConfig = ctx.obj['config'] + config: FalconToolkitConfig = ctx.obj["config"] config.remove_instance(profile_name) @profiles.command( - name='list', + name="list", help="List Falcon connection profiles.", ) @click.pass_context def profiles_list(ctx: click.Context): """Show all connection profiles that exist within the current configuration.""" - config: FalconToolkitConfig = ctx.obj['config'] + config: FalconToolkitConfig = ctx.obj["config"] config.list_instances() @profiles.command( - name='new', + name="new", help="Create a new Falcon connection profile.", ) @click.pass_context def profiles_new(ctx: click.Context): """Create a new profile, based on all loaded authentication backends.""" click.echo("New profile") - config: FalconToolkitConfig = ctx.obj['config'] + config: FalconToolkitConfig = ctx.obj["config"] config.add_instance() @click.command( - name='filters', - help='Get information on available filters', + name="filters", + help="Get information on available filters", ) def cli_list_filters(): """List all possible filters out on screen based on data available within Caracara.""" - host_filters: dict = DIALECTS['hosts'] + host_filters: dict = DIALECTS["hosts"] # We have some duplicate filters with an underscore added for FQL compatability unique_filters = set() @@ -286,8 +286,8 @@ def cli_list_filters(): unique_filters = sorted(list(unique_filters)) for unique_filter_name in unique_filters: - click.echo(click.style(unique_filter_name, fg='blue', bold=True)) - click.echo(host_filters[unique_filter_name]['help']) + click.echo(click.style(unique_filter_name, fg="blue", bold=True)) + click.echo(host_filters[unique_filter_name]["help"]) click.echo() diff --git a/falcon_toolkit/hosts/cli.py b/falcon_toolkit/hosts/cli.py index 221f7c4..8b08397 100644 --- a/falcon_toolkit/hosts/cli.py +++ b/falcon_toolkit/hosts/cli.py @@ -3,6 +3,7 @@ This file contains the command line interface for the host_search command. The implementation of the logic itself is contained in host_search.py """ + import os from typing import List @@ -19,41 +20,38 @@ @click.command( - name='host_search', - help='List hosts within the environment without connecting to them', + name="host_search", + help="List hosts within the environment without connecting to them", ) @click.pass_context @click.option( - '-e', - '--export', + "-e", + "--export", required=False, multiple=False, type=click.STRING, - help='Export data to CSV, rather than output to screen, by providing a path to this parameter', + help="Export data to CSV, rather than output to screen, by providing a path to this parameter", ) @click.option( - '-f', - '--filter', - 'filter_kv_strings', + "-f", + "--filter", + "filter_kv_strings", type=click.STRING, multiple=True, required=False, help="Filter hosts to search based on standard Falcon filters", ) @click.option( - '-o', - '--online_state', - 'online_state', + "-o", + "--online_state", + "online_state", type=click.Choice(OnlineState.VALUES), multiple=False, required=False, help="Filter hosts by online state", ) def cli_host_search( - ctx: click.Context, - filter_kv_strings: List[str], - online_state: str = None, - export: str = None + ctx: click.Context, filter_kv_strings: List[str], online_state: str = None, export: str = None ): """Implement the host_search CLI command.""" instance = get_instance(ctx) @@ -65,19 +63,23 @@ def cli_host_search( # to screen. if export is not None: if not export.endswith(".csv"): - click.echo(click.style( - f"{export} does not end in .csv. Please specify a filename ending in .csv.", - fg='red' - )) + click.echo( + click.style( + f"{export} does not end in .csv. Please specify a filename ending in .csv.", + fg="red", + ) + ) return export_dirname = os.path.dirname(os.path.abspath(export)) if not os.path.isdir(export_dirname): - click.echo(click.style( - f"The directory {export_dirname} it not a valid directory. " - "Please create this directory before exporting host data to it.", - fg='red' - )) + click.echo( + click.style( + f"The directory {export_dirname} it not a valid directory. " + "Please create this directory before exporting host data to it.", + fg="red", + ) + ) return host_search_cmd(client, filters, online_state, export) diff --git a/falcon_toolkit/hosts/host_search.py b/falcon_toolkit/hosts/host_search.py index 59548ca..01bbe27 100644 --- a/falcon_toolkit/hosts/host_search.py +++ b/falcon_toolkit/hosts/host_search.py @@ -4,6 +4,7 @@ Once a set of filters has been decided upon, swapping 'host_search' for 'shell' at the CLI will launch a batch RTR shell with these systems. """ + import csv import logging @@ -34,7 +35,7 @@ def vertically_align_middle(row_data: List[str]): new_lines = cell.count("\n") if tallest_row_height > new_lines + 1: align_line_breaks = max((tallest_row_height + 1) // 2 - 1, 0) - row_data[i] = '\0' + '\n' * align_line_breaks + cell + row_data[i] = "\0" + "\n" * align_line_breaks + cell def _host_search_export(export_path: str, host_data: Dict[str, Union[str, Dict]]) -> None: @@ -48,14 +49,11 @@ def _host_search_export(export_path: str, host_data: Dict[str, Union[str, Dict]] "role", "containment_status", "last_seen", - "grouping_tags" + "grouping_tags", ] - with open(export_path, 'w', newline='', encoding='utf-8') as csv_file_handle: - csv_writer = csv.DictWriter( - csv_file_handle, - fieldnames=fieldnames - ) + with open(export_path, "w", newline="", encoding="utf-8") as csv_file_handle: + csv_writer = csv.DictWriter(csv_file_handle, fieldnames=fieldnames) csv_writer.writeheader() @@ -63,35 +61,37 @@ def _host_search_export(export_path: str, host_data: Dict[str, Union[str, Dict]] row_data = { "aid": aid, "hostname": host_data[aid].get("hostname", ""), - "machine_domain": host_data[aid].get('machine_domain', ''), - "local_ip": host_data[aid].get('local_ip', ''), - "os_version": host_data[aid].get('os_version', ''), - "role": host_data[aid].get("product_type_desc", ''), - "containment_status": host_data[aid].get('status', 'normal'), - "last_seen": host_data[aid].get('last_seen', ''), - "grouping_tags": ';'.join(host_data[aid].get("tags", "")), + "machine_domain": host_data[aid].get("machine_domain", ""), + "local_ip": host_data[aid].get("local_ip", ""), + "os_version": host_data[aid].get("os_version", ""), + "role": host_data[aid].get("product_type_desc", ""), + "containment_status": host_data[aid].get("status", "normal"), + "last_seen": host_data[aid].get("last_seen", ""), + "grouping_tags": ";".join(host_data[aid].get("tags", "")), } csv_writer.writerow(row_data) - click.echo(click.style( - f"Successfully exported host data for {len(host_data)} hosts to {export_path}", - fg='green') + click.echo( + click.style( + f"Successfully exported host data for {len(host_data)} hosts to {export_path}", + fg="green", + ) ) def _host_search_print(host_data: Dict[str, Union[str, Dict]]) -> None: """Pretty print a list of hosts to screen in a tabular format.""" header_row = [ - click.style("Device ID", bold=True, fg='blue'), - click.style("Hostname", bold=True, fg='blue'), - click.style("Domain", bold=True, fg='blue'), - click.style("Local IP Address", bold=True, fg='blue'), - click.style("OS Version", bold=True, fg='blue'), - click.style("System Role", bold=True, fg='blue'), - click.style("Containment", bold=True, fg='blue'), - click.style("Last Seen", bold=True, fg='blue'), - click.style("Grouping Tags", bold=True, fg='blue'), + click.style("Device ID", bold=True, fg="blue"), + click.style("Hostname", bold=True, fg="blue"), + click.style("Domain", bold=True, fg="blue"), + click.style("Local IP Address", bold=True, fg="blue"), + click.style("OS Version", bold=True, fg="blue"), + click.style("System Role", bold=True, fg="blue"), + click.style("Containment", bold=True, fg="blue"), + click.style("Last Seen", bold=True, fg="blue"), + click.style("Grouping Tags", bold=True, fg="blue"), ] table_rows = [] @@ -104,29 +104,27 @@ def _host_search_print(host_data: Dict[str, Union[str, Dict]]) -> None: for aid in host_data.keys(): hostname = host_data[aid].get("hostname", "") - containment_status = host_data[aid].get('status', 'normal') - if containment_status == 'normal': - containment_str = click.style("Not Contained", fg='green') - elif containment_status == 'contained': - containment_str = click.style("Contained", fg='red') - elif containment_status == 'containment_pending': - containment_str = click.style("Pending", fg='yellow') + containment_status = host_data[aid].get("status", "normal") + if containment_status == "normal": + containment_str = click.style("Not Contained", fg="green") + elif containment_status == "contained": + containment_str = click.style("Contained", fg="red") + elif containment_status == "containment_pending": + containment_str = click.style("Pending", fg="yellow") else: containment_str = "Unknown" - grouping_tags = '\n'.join( - grouping_tag_wrap.wrap(", ".join(host_data[aid].get("tags", ""))) - ) + grouping_tags = "\n".join(grouping_tag_wrap.wrap(", ".join(host_data[aid].get("tags", "")))) row = [ - click.style(aid, fg='red'), + click.style(aid, fg="red"), click.style(hostname, bold=True), - '\n'.join(sixteen_wrap.wrap(host_data[aid].get('machine_domain', ''))), - host_data[aid].get('local_ip', ''), - '\n'.join(sixteen_wrap.wrap(host_data[aid].get('os_version', ''))), - '\n'.join(sixteen_wrap.wrap(host_data[aid].get('product_type_desc', ''))), + "\n".join(sixteen_wrap.wrap(host_data[aid].get("machine_domain", ""))), + host_data[aid].get("local_ip", ""), + "\n".join(sixteen_wrap.wrap(host_data[aid].get("os_version", ""))), + "\n".join(sixteen_wrap.wrap(host_data[aid].get("product_type_desc", ""))), containment_str, - host_data[aid].get('last_seen', '').replace('T', '\n').replace('Z', ''), + host_data[aid].get("last_seen", "").replace("T", "\n").replace("Z", ""), grouping_tags, ] table_rows.append(row) @@ -138,20 +136,19 @@ def _host_search_print(host_data: Dict[str, Union[str, Dict]]) -> None: table_rows.insert(0, header_row) - click.echo(tabulate.tabulate( - table_rows, - tablefmt='fancy_grid', - )) + click.echo( + tabulate.tabulate( + table_rows, + tablefmt="fancy_grid", + ) + ) def host_search_cmd( - client: Client, - filters: FalconFilter, - online_state: Optional[str], - export: Optional[str] + client: Client, filters: FalconFilter, online_state: Optional[str], export: Optional[str] ): """Search for hosts that match the provided filters.""" - click.echo(click.style("Searching for hosts...", fg='magenta')) + click.echo(click.style("Searching for hosts...", fg="magenta")) fql = filters.get_fql() diff --git a/falcon_toolkit/maintenance_token/cli.py b/falcon_toolkit/maintenance_token/cli.py index 7726808..9db4c60 100644 --- a/falcon_toolkit/maintenance_token/cli.py +++ b/falcon_toolkit/maintenance_token/cli.py @@ -2,6 +2,7 @@ This file contains the CLI options for the falcon maintenance_token command. """ + import logging from typing import List @@ -23,7 +24,7 @@ @click.command( name="maintenance_token", - help="Get the maintenance token for a device, or get the bulk maintenance token" + help="Get the maintenance token for a device, or get the bulk maintenance token", ) @click.pass_context @optgroup.group( @@ -32,25 +33,25 @@ help="Choose no more than one method to choose systems to fetch the maintenance tokens for", ) @optgroup.option( - '-b', - '--bulk', - 'bulk_token', + "-b", + "--bulk", + "bulk_token", type=click.BOOL, is_flag=True, default=False, help="Get the CID-wide bulk maintenance token", ) @optgroup.option( - '-d', - '--device-id-list', - 'device_id_list', + "-d", + "--device-id-list", + "device_id_list", type=click.STRING, - help="Specify a list of Device IDs (AIDs), comma delimited" + help="Specify a list of Device IDs (AIDs), comma delimited", ) @optgroup.option( - '-df', - '--device-id-file', - 'device_id_file', + "-df", + "--device-id-file", + "device_id_file", type=click.STRING, help=( "Specify a list of Device IDs (AIDs) in an external file, one per line; " @@ -58,9 +59,9 @@ ), ) @optgroup.option( - '-f', - '--filter', - 'filter_kv_string', + "-f", + "--filter", + "filter_kv_string", type=click.STRING, multiple=True, help="Filter hosts based on standard Falcon filters", @@ -75,35 +76,42 @@ def cli_maintenance_token( """Get system maintenance tokens from Falcon.""" instance = get_instance(ctx) client: Client = instance.auth_backend.authenticate() - ctx.obj['client'] = client + ctx.obj["client"] = client # Bulk token is a special case we can handle here. # Device tokens need to be handled elsewhere. if bulk_token: - click.echo(click.style( - "Getting the bulk maintenance token", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Getting the bulk maintenance token", + fg="magenta", + bold=True, + ) + ) token = client.sensor_update_policies.get_bulk_maintenance_token( audit_message="Fetched via Falcon Toolkit", ) click.echo("Bulk maintenance token: ", nl=False) - click.echo(click.style(token, bold=True, fg='blue')) - click.echo(click.style( - "WARNING: this token must be kept safe, as it can uninstall all Falcon sensors!", - bold=True, - fg='red', - )) + click.echo(click.style(token, bold=True, fg="blue")) + click.echo( + click.style( + "WARNING: this token must be kept safe, as it can uninstall all Falcon sensors!", + bold=True, + fg="red", + ) + ) return if filter_kv_string: - click.echo(click.style( - "Getting the maintenance tokens for all hosts that match the provided Falcon filters", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Getting the maintenance tokens for all hosts that match the provided" + " Falcon filters", + fg="magenta", + bold=True, + ) + ) logging.info("Getting maintenance tokens for all devices that match the provided filters") filters = parse_cli_filters(filter_kv_string, client).get_fql() @@ -114,12 +122,14 @@ def cli_maintenance_token( device_ids = client.hosts.get_device_ids(filters=filters) elif device_id_list: - click.echo(click.style( - "Getting the maintenance tokens for the devices identified by the IDs provided on " - "the command line", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Getting the maintenance tokens for the devices identified by the IDs provided on " + "the command line", + fg="magenta", + bold=True, + ) + ) logging.info( "Getting the maintenance tokens for the devices identified by the IDs " "provided on the command line" @@ -132,19 +142,22 @@ def cli_maintenance_token( device_ids.add(device_id) elif device_id_file: - click.echo(click.style( - "Getting the maintenance tokens for the devices identified by the IDs listed in a file", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Getting the maintenance tokens for the devices identified by the IDs listed in a" + " file", + fg="magenta", + bold=True, + ) + ) click.echo(click.style("File path: ", bold=True), nl=False) click.echo(device_id_file) logging.info( "Getting the maintenance tokens for the devices identified by the IDs listed in %s", - device_id_file + device_id_file, ) - with open(device_id_file, 'rt', encoding='ascii') as device_id_file_handle: + with open(device_id_file, "rt", encoding="ascii") as device_id_file_handle: device_ids = set() for line in device_id_file_handle: line = line.strip() @@ -152,12 +165,14 @@ def cli_maintenance_token( device_ids.add(line) else: - click.echo(click.style( - "Getting the maintenance token for all systems in the tenant!", - bold=True, - fg='yellow', - )) - click.echo("You must enter the string \"I AM SURE!\" to proceed.") + click.echo( + click.style( + "Getting the maintenance token for all systems in the tenant!", + bold=True, + fg="yellow", + ) + ) + click.echo('You must enter the string "I AM SURE!" to proceed.') confirmation = input("Are you sure? ") if confirmation != "I AM SURE!": print("You did not confirm you were sure. Aborting!") @@ -172,8 +187,10 @@ def cli_maintenance_token( client=client, ) else: - click.echo(click.style( - "No devices matched the provided filters", - fg='red', - bold=True, - )) + click.echo( + click.style( + "No devices matched the provided filters", + fg="red", + bold=True, + ) + ) diff --git a/falcon_toolkit/maintenance_token/device_tokens.py b/falcon_toolkit/maintenance_token/device_tokens.py index f0844da..4a4aac9 100644 --- a/falcon_toolkit/maintenance_token/device_tokens.py +++ b/falcon_toolkit/maintenance_token/device_tokens.py @@ -2,6 +2,7 @@ This file contains the logic required to fetch tokens for many devices and write them to screen. """ + import logging from operator import itemgetter @@ -23,8 +24,8 @@ def show_device_maintenance_tokens( tokens = {} header_row = [ - click.style("Device ID", bold=True, fg='blue'), - click.style("Maintenance Token", bold=True, fg='blue'), + click.style("Device ID", bold=True, fg="blue"), + click.style("Maintenance Token", bold=True, fg="blue"), ] tokens_table = [] @@ -36,14 +37,18 @@ def show_device_maintenance_tokens( ) logging.debug("%s -> %s", device_id, token) tokens[device_id] = token - tokens_table.append([ - device_id, - token, - ]) + tokens_table.append( + [ + device_id, + token, + ] + ) tokens_table = sorted(tokens_table, key=itemgetter(1, 0)) tokens_table.insert(0, header_row) - click.echo(tabulate.tabulate( - tokens_table, - tablefmt='fancy_grid', - )) + click.echo( + tabulate.tabulate( + tokens_table, + tablefmt="fancy_grid", + ) + ) diff --git a/falcon_toolkit/policies/cli.py b/falcon_toolkit/policies/cli.py index ea512ce..dcce343 100644 --- a/falcon_toolkit/policies/cli.py +++ b/falcon_toolkit/policies/cli.py @@ -3,6 +3,7 @@ This file contains the command line interface for the policies commands. The implementation of the logic itself is contained in other files, including policies.py """ + import os import sys @@ -26,8 +27,8 @@ @click.group( - name='policies', - help='Manage Falcon Prevention and Response policies', + name="policies", + help="Manage Falcon Prevention and Response policies", ) @click.pass_context @optgroup.group( @@ -36,16 +37,16 @@ help="Choose whether to interface with [-p]revention or [-r]esponse policies", ) @optgroup.option( - '-p', - '--prevention', - 'prevention_policies_option', + "-p", + "--prevention", + "prevention_policies_option", is_flag=True, help="Interface with Prevention policies", ) @optgroup.option( - '-r', - '--response', - 'response_policies_option', + "-r", + "--response", + "response_policies_option", is_flag=True, help="Interface with Response policies", ) @@ -57,42 +58,39 @@ def cli_policies( """Configure the future profiles commands by getting the context in shape.""" instance = get_instance(ctx) client: Client = instance.auth_backend.authenticate() - ctx.obj['client'] = client + ctx.obj["client"] = client if prevention_policies_option: - ctx.obj['policies_api'] = client.prevention_policies - ctx.obj['policies_type'] = "prevention" + ctx.obj["policies_api"] = client.prevention_policies + ctx.obj["policies_type"] = "prevention" elif response_policies_option: - ctx.obj['policies_api'] = client.response_policies - ctx.obj['policies_type'] = "response" + ctx.obj["policies_api"] = client.response_policies + ctx.obj["policies_type"] = "response" else: raise ValueError("Impossible scenario: no policy type specified") -@click.command( - name='describe', - help='List and describe the policies within the Falcon tenant.' -) +@click.command(name="describe", help="List and describe the policies within the Falcon tenant.") @click.pass_context def policies_describe(ctx: click.Context): """List and describe the Prevention or Response policies within the Falcon tenant.""" - policies_api: PoliciesApiModule = ctx.obj['policies_api'] - policies_type: str = ctx.obj['policies_type'] - click.echo(click.style(f"Describing all {policies_type} policies", fg='green', bold=True)) + policies_api: PoliciesApiModule = ctx.obj["policies_api"] + policies_type: str = ctx.obj["policies_type"] + click.echo(click.style(f"Describing all {policies_type} policies", fg="green", bold=True)) policies = policies_api.describe_policies() pretty_print_policies(policies) @click.command( - name='export', - help='Export a Prevention or Response policy to disk.', + name="export", + help="Export a Prevention or Response policy to disk.", ) @click.pass_context def policies_export(ctx: click.Context): """Allow a user to choose a Prevention or Response policy to export to disk.""" # pylint: disable=too-many-locals - policies_api: PoliciesApiModule = ctx.obj['policies_api'] - policies_type: str = ctx.obj['policies_type'] + policies_api: PoliciesApiModule = ctx.obj["policies_api"] + policies_type: str = ctx.obj["policies_type"] click.echo("Loading policies...") policies: List[Policy] = policies_api.describe_policies() @@ -116,11 +114,11 @@ def policies_export(ctx: click.Context): while not reasonable_filename: filename: str = click.prompt("Policy filename", type=str, default=default_filename) if not filename.endswith(".json"): - click.echo(click.style("Filename must end in .json", fg='yellow')) + click.echo(click.style("Filename must end in .json", fg="yellow")) continue if os.path.exists(filename): - click.echo(click.style("File already exists!", fg='yellow')) + click.echo(click.style("File already exists!", fg="yellow")) continue reasonable_filename = True @@ -130,19 +128,19 @@ def policies_export(ctx: click.Context): policy_type=policies_type, ) - with open(filename, 'wt', encoding='utf-8') as export_file_handle: + with open(filename, "wt", encoding="utf-8") as export_file_handle: export_file_handle.write(policy_container.dumps()) click.echo("Export complete") @click.command( - name='import', - help='Import a Prevention or Response policy from disk.', + name="import", + help="Import a Prevention or Response policy from disk.", ) @click.pass_context @click.argument( - 'filename', + "filename", type=click.STRING, ) def policies_import( @@ -150,11 +148,11 @@ def policies_import( filename: str, ): """Import a Prevention or Response policy from the JSON file named FILENAME.""" - policies_api: PoliciesApiModule = ctx.obj['policies_api'] + policies_api: PoliciesApiModule = ctx.obj["policies_api"] click.echo(f"Loading policy in the file: {filename}") - with open(filename, 'rt', encoding='utf-8') as policy_file_handle: + with open(filename, "rt", encoding="utf-8") as policy_file_handle: policy_str = str(policy_file_handle.read()) policy_container = PolicyContainer.loads(policy_str) diff --git a/falcon_toolkit/policies/constants.py b/falcon_toolkit/policies/constants.py index 749d027..e532f53 100644 --- a/falcon_toolkit/policies/constants.py +++ b/falcon_toolkit/policies/constants.py @@ -3,6 +3,7 @@ This file contains constants that are required to properly display and manage Prevention and Response policies within a Falcon tenant. """ + from typing import Union from caracara.modules.prevention_policies import PreventionPoliciesApiModule diff --git a/falcon_toolkit/policies/container.py b/falcon_toolkit/policies/container.py index bfff50b..4754afe 100644 --- a/falcon_toolkit/policies/container.py +++ b/falcon_toolkit/policies/container.py @@ -3,6 +3,7 @@ This file contains a data model to hold a poilicy object, along with the associated Falcon Toolkit-specific metadata required to ensure that an import will succeed. """ + import json from typing import Dict, List, Type @@ -58,20 +59,19 @@ def loads(cls: Type["PolicyContainer"], policy_container_dump: str) -> "PolicyCo ) try: - enabled: bool = import_data['enabled'] - name: str = import_data['name'] - platform_name: str = import_data['platform_name'] - policy_type: str = import_data['policy_type'] - settings_key_name: str = import_data['settings_key_name'] - settings_groups: List[Dict] = import_data['settings_groups'] + enabled: bool = import_data["enabled"] + name: str = import_data["name"] + platform_name: str = import_data["platform_name"] + policy_type: str = import_data["policy_type"] + settings_key_name: str = import_data["settings_key_name"] + settings_groups: List[Dict] = import_data["settings_groups"] except KeyError as exc: raise KeyError("Policy export does not contain all the required fields.") from exc if policy_type not in POLICY_TYPES: raise ValueError( "Policy is not of a supported type. This version of Falcon Toolkit " - "only supports these policy types: " + - str(POLICY_TYPES) + "only supports these policy types: " + str(POLICY_TYPES) ) description: str = import_data.get("description", "Imported by Falcon Toolkit") diff --git a/falcon_toolkit/policies/describe.py b/falcon_toolkit/policies/describe.py index 341c277..71f74cf 100644 --- a/falcon_toolkit/policies/describe.py +++ b/falcon_toolkit/policies/describe.py @@ -2,6 +2,7 @@ This code file contains all the logic required to describe policies in a visually clean way. """ + from textwrap import TextWrapper from typing import List @@ -24,10 +25,10 @@ def pretty_print_policies(policies: List[Policy]): """Format a list of Prevention or Detection policies neatly and write them out to screen.""" for policy in policies: click.echo( - click.style(policy.name, bold=True) + - " (Platform: " + - click.style(policy.platform_name, fg='red') + - ")" + click.style(policy.name, bold=True) + + " (Platform: " + + click.style(policy.platform_name, fg="red") + + ")" ) if policy.description: click.echo(f" {policy.description}") @@ -58,10 +59,10 @@ def pretty_print_policies(policies: List[Policy]): setting_cells = [] setting_title = ( - click.style(setting.name, bold=True) + - "\n" + - '\n'.join(setting_desc_wrap.wrap(setting.description)) + - "\n" + click.style(setting.name, bold=True) + + "\n" + + "\n".join(setting_desc_wrap.wrap(setting.description)) + + "\n" ) if isinstance(setting, TogglePolicySetting): # Toggles have an ON or OFF button ASCII graphic available @@ -82,12 +83,14 @@ def pretty_print_policies(policies: List[Policy]): settings_table.append(row) # Write the table to screen - click.echo(tabulate.tabulate( - settings_table, - tablefmt="fancy_grid", - )) + click.echo( + tabulate.tabulate( + settings_table, + tablefmt="fancy_grid", + ) + ) # Draw some blank space and a divider line to go between Policies click.echo() - click.echo("-"*80) + click.echo("-" * 80) click.echo() diff --git a/falcon_toolkit/shell/cli.py b/falcon_toolkit/shell/cli.py index 4461653..220df33 100644 --- a/falcon_toolkit/shell/cli.py +++ b/falcon_toolkit/shell/cli.py @@ -3,6 +3,7 @@ This file contains the CLI options for the shell command, which allows a user to invoke a batch RTR shell with many connected systems at once. """ + import logging import logging.handlers import sys @@ -26,26 +27,26 @@ @click.command( - name='shell', - help='Create a Real Time Response batch shell', + name="shell", + help="Create a Real Time Response batch shell", ) @click.pass_context @optgroup.group( "Specify devices", cls=MutuallyExclusiveOptionGroup, - help="Choose no more than one method to choose systems to connect to" + help="Choose no more than one method to choose systems to connect to", ) @optgroup.option( - '-d', - '--device-id-list', - 'device_id_list', + "-d", + "--device-id-list", + "device_id_list", type=click.STRING, help="Specify a list of Device IDs (AIDs) to connect to, comma delimited", ) @optgroup.option( - '-df', - '--device-id-file', - 'device_id_file', + "-df", + "--device-id-file", + "device_id_file", type=click.STRING, help=( "Specify a list of Device IDs (AIDs) to connect to in an external file, one per line; " @@ -53,29 +54,29 @@ ), ) @optgroup.option( - '-f', - '--filter', - 'filter_kv_strings', + "-f", + "--filter", + "filter_kv_strings", type=click.STRING, multiple=True, help="Filter hosts to connect to based on standard Falcon filters", ) @optgroup.group( "RTR Connection Options", - help="General connection options for the Real Time Response (RTR) batch session." + help="General connection options for the Real Time Response (RTR) batch session.", ) @optgroup.option( - '-q', - '--queueing', + "-q", + "--queueing", type=click.BOOL, is_flag=True, default=False, help="Enable RTR Queueing (default: off)", ) @optgroup.option( - '-s', - '--script', - 'startup_script', + "-s", + "--script", + "startup_script", required=False, type=click.STRING, help=( @@ -86,8 +87,8 @@ ), ) @optgroup.option( - '-t', - '--timeout', + "-t", + "--timeout", type=click.INT, default=30, required=False, @@ -128,11 +129,13 @@ def cli_shell( # pylint: disable=too-many-arguments,too-many-locals online_string = "" if queueing else "online " if filter_kv_strings: - click.echo(click.style( - "Connecting to all hosts that match the provided Falcon filters", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Connecting to all hosts that match the provided Falcon filters", + fg="magenta", + bold=True, + ) + ) logging.info("Connecting to all hosts that match the provided Falcon filters") filters = parse_cli_filters(filter_kv_strings, client).get_fql() @@ -142,11 +145,13 @@ def cli_shell( # pylint: disable=too-many-arguments,too-many-locals device_ids = client.hosts.get_device_ids(filters=filters, online_state=online_state) elif device_id_list: - click.echo(click.style( - "Connecting to the device IDs provided on the command line", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Connecting to the device IDs provided on the command line", + fg="magenta", + bold=True, + ) + ) logging.info("Connecting to the device IDs provided on the command line") device_ids = set() @@ -160,16 +165,18 @@ def cli_shell( # pylint: disable=too-many-arguments,too-many-locals online_state=online_state, ) elif device_id_file: - click.echo(click.style( - "Connecting to the device IDs listed in a file", - fg='magenta', - bold=True, - )) + click.echo( + click.style( + "Connecting to the device IDs listed in a file", + fg="magenta", + bold=True, + ) + ) click.echo(click.style("File path: ", bold=True), nl=False) click.echo(device_id_file) logging.info("Connecting to the device IDs listed in %s", device_id_file) - with open(device_id_file, 'rt', encoding='ascii') as device_id_file_handle: + with open(device_id_file, "rt", encoding="ascii") as device_id_file_handle: device_ids = set() for line in device_id_file_handle: line = line.strip() @@ -180,19 +187,23 @@ def cli_shell( # pylint: disable=too-many-arguments,too-many-locals online_state=online_state, ) else: - click.echo(click.style( - f"WARNING: Connecting to all {online_string}hosts in the Falcon instance", - fg='yellow', - )) + click.echo( + click.style( + f"WARNING: Connecting to all {online_string}hosts in the Falcon instance", + fg="yellow", + ) + ) logging.info("Connecting to all %shosts in the Falcon instance", online_string) device_ids = client.hosts.get_device_ids(online_state=online_state) if not device_ids: - click.echo(click.style( - f"No {online_string}devices match the provided filters", - fg='red', - bold=True, - )) + click.echo( + click.style( + f"No {online_string}devices match the provided filters", + fg="red", + bold=True, + ) + ) sys.exit(1) device_count = len(device_ids) @@ -200,7 +211,7 @@ def cli_shell( # pylint: disable=too-many-arguments,too-many-locals logging.info("Connecting to %d device(s)", device_count) logging.debug(device_ids) - log_filename_base = ctx.obj['log_filename_base'] + log_filename_base = ctx.obj["log_filename_base"] csv_filename = f"{log_filename_base}.csv" prompt = RTRPrompt( diff --git a/falcon_toolkit/shell/cmd_generators/reg.py b/falcon_toolkit/shell/cmd_generators/reg.py index a140372..325efc0 100644 --- a/falcon_toolkit/shell/cmd_generators/reg.py +++ b/falcon_toolkit/shell/cmd_generators/reg.py @@ -3,6 +3,7 @@ This file contains a command generator for the reg command, which is particularly complex to parse in one function. """ + from argparse import Namespace from falcon_toolkit.shell.cmd_generators.common import CommandBuilderException @@ -11,17 +12,17 @@ def _reg_delete_builder(args: Namespace) -> str: """Build a reg delete command string.""" if args.value: - return f'reg delete {args.subkey} {args.value}' + return f"reg delete {args.subkey} {args.value}" - return f'reg delete {args.subkey}' + return f"reg delete {args.subkey}" def _reg_load_builder(args: Namespace) -> str: """Build a reg load command string.""" if args.troubleshooting: - return f'reg load {args.filename} {args.subkey} -Troubleshooting' + return f"reg load {args.filename} {args.subkey} -Troubleshooting" - return f'reg load {args.filename} {args.subkey}' + return f"reg load {args.filename} {args.subkey}" def _reg_query_builder(args: Namespace) -> str: @@ -30,12 +31,12 @@ def _reg_query_builder(args: Namespace) -> str: raise CommandBuilderException("You must specify a value name, type and data together") if args.subkey and args.value: - return f'reg query {args.subkey} {args.value}' + return f"reg query {args.subkey} {args.value}" if args.subkey and not args.value: - return f'reg query {args.subkey}' + return f"reg query {args.subkey}" - return 'reg query' + return "reg query" def _reg_set_builder(args: Namespace) -> str: @@ -43,21 +44,21 @@ def _reg_set_builder(args: Namespace) -> str: if args.value_name or args.value_type or args.data: if args.value_name and args.value_type and args.data: return ( - f'reg set {args.subkey} {args.value_name} ' - f'-ValueType={args.value_type} -Value={args.data}' + f"reg set {args.subkey} {args.value_name} " + f"-ValueType={args.value_type} -Value={args.data}" ) raise CommandBuilderException("You must specify a value name, type and data together") - return f'reg set {args.subkey}' + return f"reg set {args.subkey}" def _reg_unload_builder(args: Namespace) -> str: """Build a reg unload command string.""" if args.troubleshooting: - return f'reg unload {args.subkey} -Troubleshooting' + return f"reg unload {args.subkey} -Troubleshooting" - return f'reg unload {args.subkey}' + return f"reg unload {args.subkey}" def reg_builder(args: Namespace) -> str: diff --git a/falcon_toolkit/shell/parsers.py b/falcon_toolkit/shell/parsers.py index afcdbfd..c2432f8 100644 --- a/falcon_toolkit/shell/parsers.py +++ b/falcon_toolkit/shell/parsers.py @@ -4,6 +4,7 @@ each command. Each command that exists in RTR within the Cloud must be implemented here for the shell to expose it to a user. """ + import functools import os @@ -19,52 +20,46 @@ cat_argparser = Cmd2ArgumentParser() cat_argparser.add_argument( - 'file', + "file", help="File to read the contents of", ) cat_argparser.add_argument( - '-b', - '--ShowHex', - help='Show the results in hexadecimal byte format instead of ASCII', - dest='show_hex', - action='store_true', + "-b", + "--ShowHex", + help="Show the results in hexadecimal byte format instead of ASCII", + dest="show_hex", + action="store_true", ) cd_argparser = Cmd2ArgumentParser() cd_argparser.add_argument( - 'directory', + "directory", help="Directory to change to", ) cloud_scripts_argparser = Cmd2ArgumentParser() cloud_scripts_argparser.add_argument( - '-s', - '--show-content', - help=( - "Show the content of each script on-screen " - "(may produce long outputs!)" - ), - dest='show_content', - action='store_true', + "-s", + "--show-content", + help=("Show the content of each script on-screen (may produce long outputs!)"), + dest="show_content", + action="store_true", ) cloud_scripts_argparser.add_argument( - 'script_name', - help=( - "Only show information about a specific script " - "(default: all scripts)" - ), - nargs='?', + "script_name", + help=("Only show information about a specific script (default: all scripts)"), + nargs="?", choices=CLOUD_SCRIPT_CHOICES, ) cp_argparser = Cmd2ArgumentParser() cp_argparser.add_argument( - 'source', + "source", help="Source File or Directory", ) cp_argparser.add_argument( - 'destination', + "destination", help="Destination File or Directory", ) @@ -74,30 +69,30 @@ path_filter=lambda path: os.path.isdir(path), # pylint: disable=unnecessary-lambda ) download_argparser.add_argument( - 'destination', + "destination", help="Destination directory on the local system to download files to", completer=download_dir_completer, ) download_argparser.add_argument( - '-b', - '--batch-get-req-id', + "-b", + "--batch-get-req-id", help="Batch request ID (defaults to the most recent get command)", - dest='batch_get_req_id', + dest="batch_get_req_id", type=str, - nargs='?', + nargs="?", ) download_argparser.add_argument( - '-e', - '--extract', + "-e", + "--extract", help="Extract and delete the downloaded 7z archive, leaving only the retrieved file itself", - action='store_true', - dest='extract_7z', + action="store_true", + dest="extract_7z", ) eventlog_argparser = Cmd2ArgumentParser() eventlog_subparsers: Cmd2ArgumentParser = eventlog_argparser.add_subparsers( - title='Commands', - description='Inspect event logs', + title="Commands", + description="Inspect event logs", dest="command_name", required=True, ) @@ -140,326 +135,320 @@ eventlog_parser_view.add_argument( "count", type=int, - nargs='?', + nargs="?", help="Number of entries to return. Default: 100; Maximum: 500", ) eventlog_parser_view.add_argument( - "source_name", - nargs='?', - help="Name of the event source, e.g. 'WinLogon'" + "source_name", nargs="?", help="Name of the event source, e.g. 'WinLogon'" ) ls_argparser = Cmd2ArgumentParser() ls_argparser.add_argument( - 'directory', - default='.', - nargs='?', + "directory", + default=".", + nargs="?", help="Directory to list", ) ls_argparser.add_argument( - '-l', - '--long', + "-l", + "--long", help="[Linux] List in long format", - action='store_true', - dest='long_format', + action="store_true", + dest="long_format", ) ls_argparser.add_argument( - '-L', - '--follow-symlinks', + "-L", + "--follow-symlinks", help=( "[Linux] Follow all symbolic links to final target and " "list the file or directory the link references, " "rather than the link itself" ), - action='store_true', - dest='follow_symlinks', + action="store_true", + dest="follow_symlinks", ) ls_argparser.add_argument( - '-R', - '--recurse', + "-R", + "--recurse", help="[Linux] Recursively list subdirectories encountered", - action='store_true', - dest='recursive', + action="store_true", + dest="recursive", ) ls_argparser.add_argument( - '-T', - '--time-modified', + "-T", + "--time-modified", help=( "[Linux] Sort by time modified (most recently modified first), " "before sorting the operands by lexicographical order" ), - action='store_true', - dest='sort_time_modified', + action="store_true", + dest="sort_time_modified", ) encrypt_argparser = Cmd2ArgumentParser() encrypt_argparser.add_argument( - 'path', + "path", help="File to encrypt", ) encrypt_argparser.add_argument( - 'key', - nargs='?', - help='Base64 encoded encryption key (optional)', + "key", + nargs="?", + help="Base64 encoded encryption key (optional)", ) env_argparser = Cmd2ArgumentParser() filehash_argparser = Cmd2ArgumentParser() filehash_argparser.add_argument( - 'file', + "file", help="File to calculate hashes for", ) get_argparser = Cmd2ArgumentParser() get_argparser.add_argument( - 'file', + "file", help="Path to file to be uploaded to the CrowdStrike cloud", ) get_status_argparser = Cmd2ArgumentParser() get_status_argparser.add_argument( - 'batch_get_req_id', - nargs='?', - help='ID of batch request to get. Defaults to the last batch request.', + "batch_get_req_id", + nargs="?", + help="ID of batch request to get. Defaults to the last batch request.", ) kill_argparser = Cmd2ArgumentParser() -kill_argparser.add_argument( - 'pid', help="Process ID" -) +kill_argparser.add_argument("pid", help="Process ID") map_argparser = Cmd2ArgumentParser() map_argparser.add_argument( - 'drive_letter', + "drive_letter", help="Drive letter (with or without ':')", ) map_argparser.add_argument( - 'network_share', - help='UNC path of remote share', + "network_share", + help="UNC path of remote share", ) map_argparser.add_argument( - 'username', - help='User account used for the connection', + "username", + help="User account used for the connection", ) map_argparser.add_argument( - 'password', - help='Password for the user account', + "password", + help="Password for the user account", ) memdump_argparser = Cmd2ArgumentParser() memdump_argparser.add_argument( - 'pid', - help=( - 'PID (Process ID) of the process. ' - 'Use the "ps" command to discover possible values' - ), + "pid", + help=("PID (Process ID) of the process. " 'Use the "ps" command to discover possible values'), ) memdump_argparser.add_argument( - 'filename', - help='Absolute or relative path for dump output file', + "filename", + help="Absolute or relative path for dump output file", ) mkdir_argparser = Cmd2ArgumentParser() mkdir_argparser.add_argument( - 'directory', + "directory", help="Name of new directory to create", ) mount_argparser = Cmd2ArgumentParser() mount_subparsers = mount_argparser.add_subparsers( - title='[Linux/macOS] Mount a filesystem', - description='Mount a filesystem on Linux or macOS. The Windows equivalent is the map command', + title="[Linux/macOS] Mount a filesystem", + description="Mount a filesystem on Linux or macOS. The Windows equivalent is the map command", required=False, ) mount_macos_subparser = mount_subparsers.add_parser( - 'source', help='Source filesystem, possibly a URL including username and password', + "source", + help="Source filesystem, possibly a URL including username and password", ) mount_macos_subparser.add_argument( - 'mount_point', - help='Path to the desired mount point', + "mount_point", + help="Path to the desired mount point", ) mount_macos_subparser.add_argument( - '-t', + "-t", required=False, - dest='filesystem_type', - help='Filesystem type (e.g., nfs, smbfs)', + dest="filesystem_type", + help="Filesystem type (e.g., nfs, smbfs)", nargs=1, ) mount_macos_subparser.add_argument( - '-o', + "-o", required=False, - dest='mount_options', - help='Mount options (e.g., nobrowse)', + dest="mount_options", + help="Mount options (e.g., nobrowse)", nargs=1, ) mv_argparser = Cmd2ArgumentParser() mv_argparser.add_argument( - 'source', - help='Source file or directory. Absolute or relative path.', + "source", + help="Source file or directory. Absolute or relative path.", ) mv_argparser.add_argument( - 'destination', - help='Destination. Absolute or relative path.', + "destination", + help="Destination. Absolute or relative path.", ) netstat_argparser = Cmd2ArgumentParser() netstat_argparser.add_argument( - '-nr', - help='Show routing information', - dest='routing_info', - action='store_true', + "-nr", + help="Show routing information", + dest="routing_info", + action="store_true", ) put_argparser = Cmd2ArgumentParser() put_argparser.add_argument( - 'file', - help='Name of the file to download to the host from the CrowdStrike Cloud', + "file", + help="Name of the file to download to the host from the CrowdStrike Cloud", choices=PUT_FILE_CHOICES, ) put_and_run_argparser = Cmd2ArgumentParser() put_and_run_argparser.add_argument( - 'file', + "file", help=( - '[Windows] Name of the file to download to the host from the CrowdStrike Cloud, ' - 'and consequently execute. The file will be executed from the directory: ' - 'C:\\windows\\system32\\drivers\\crowdstrike\\rtr\\putrun.' + "[Windows] Name of the file to download to the host from the CrowdStrike Cloud, " + "and consequently execute. The file will be executed from the directory: " + "C:\\windows\\system32\\drivers\\crowdstrike\\rtr\\putrun." ), choices=PUT_FILE_CHOICES, ) reg_argparser = Cmd2ArgumentParser() reg_subparsers: Cmd2ArgumentParser = reg_argparser.add_subparsers( - title='Registry Inspection and Manipulation', - description='Choose how to inspect or manipulate the registry', - dest='command_name', + title="Registry Inspection and Manipulation", + description="Choose how to inspect or manipulate the registry", + dest="command_name", required=True, ) reg_delete_parser: Cmd2ArgumentParser = reg_subparsers.add_parser( - 'delete', - help='Delete registry subkeys, keys or values', + "delete", + help="Delete registry subkeys, keys or values", ) reg_delete_parser.add_argument( - 'subkey', - help='Registry subkey full path', + "subkey", + help="Registry subkey full path", ) reg_delete_parser.add_argument( - 'value', - help='If provided, delete only this value', - nargs='?', + "value", + help="If provided, delete only this value", + nargs="?", ) reg_load_parser: Cmd2ArgumentParser = reg_subparsers.add_parser( - 'load', - help='Load a user registry hive from disk', + "load", + help="Load a user registry hive from disk", ) reg_load_parser.add_argument( - 'filename', + "filename", help='Path to user registry hive (e.g. "C:\\Users\\paul\\NTUSER.DAT")', ) reg_load_parser.add_argument( - 'subkey', + "subkey", help='Registry subkey destination (e.g. "HKEY_USERS\\paul-temp")', ) reg_load_parser.add_argument( - '-Troubleshooting', - dest='troubleshooting', - help='Flag to print verbose error messages for escalation to support', - action='store_true', + "-Troubleshooting", + dest="troubleshooting", + help="Flag to print verbose error messages for escalation to support", + action="store_true", ) reg_query_parser: Cmd2ArgumentParser = reg_subparsers.add_parser( - 'query', - help='Query a registry subkey or value', + "query", + help="Query a registry subkey or value", ) reg_query_parser.add_argument( - 'subkey', - help='Registry subkey full path', + "subkey", + help="Registry subkey full path", ) reg_query_parser.add_argument( - 'value', - nargs='?', - help='Name of value to query (requires a subkey argument)', + "value", + nargs="?", + help="Name of value to query (requires a subkey argument)", ) reg_set_parser: Cmd2ArgumentParser = reg_subparsers.add_parser( - 'set', - help='Set registry keys or values. NB: syntax differs from Falcon UI.', + "set", + help="Set registry keys or values. NB: syntax differs from Falcon UI.", ) reg_set_parser.add_argument( - 'subkey', - help='Registry subkey full path', + "subkey", + help="Registry subkey full path", ) reg_set_parser.add_argument( "-Value", - dest='value_name', - help='Name of value to set', + dest="value_name", + help="Name of value to set", ) reg_set_parser.add_argument( - '-ValueType', - dest='value_type', - help='Type of value', - choices=['REG_SZ', 'REG_DWORD', 'REG_QWORD', 'REG_MULTI_SZ', 'REG_BINARY'], + "-ValueType", + dest="value_type", + help="Type of value", + choices=["REG_SZ", "REG_DWORD", "REG_QWORD", "REG_MULTI_SZ", "REG_BINARY"], type=str.upper, ) reg_set_parser.add_argument( - '-ValueData', - dest='data', - help='Contents of value to insert into Registry', + "-ValueData", + dest="data", + help="Contents of value to insert into Registry", ) reg_unload_parser: Cmd2ArgumentParser = reg_subparsers.add_parser( - 'unload', - help='Unload a previously loaded user registry hive', + "unload", + help="Unload a previously loaded user registry hive", ) reg_unload_parser.add_argument( - 'subkey', + "subkey", help='Registry subkey to unload (e.g. "HKEY_USERS\\paul-temp")', ) reg_unload_parser.add_argument( - '-Troubleshooting', - dest='troubleshooting', - help='Flag to print verbose error messages for escalation to support', - action='store_true', + "-Troubleshooting", + dest="troubleshooting", + help="Flag to print verbose error messages for escalation to support", + action="store_true", ) restart_argparser = Cmd2ArgumentParser() restart_argparser.add_argument( - '-Confirm', - dest='confirm', - help='Confirms restart is ok', - action='store_true', + "-Confirm", + dest="confirm", + help="Confirms restart is ok", + action="store_true", ) rm_argparser = Cmd2ArgumentParser() rm_argparser.add_argument( - 'path', - help='File or directory to delete', + "path", + help="File or directory to delete", ) rm_argparser.add_argument( - '-Force', - dest='force', - help='Flag to allow directory and recursive deletes', - action='store_true', + "-Force", + dest="force", + help="Flag to allow directory and recursive deletes", + action="store_true", ) run_argparser = Cmd2ArgumentParser() run_argparser.add_argument( "executable", - help='The absolute path to the executable', + help="The absolute path to the executable", ) run_argparser.add_argument( - '-CommandLine', - dest='command_line_args', - help='Command line arguments passed to the executable', + "-CommandLine", + dest="command_line_args", + help="Command line arguments passed to the executable", ) run_argparser.add_argument( - '-Wait', - dest='wait', + "-Wait", + dest="wait", help=( - 'Run the program and wait for the result code. ' - 'The default behaviour (i.e. without the -Wait option) ' - 'is to start the program and return without waiting for ' - 'the result code.' + "Run the program and wait for the result code. " + "The default behaviour (i.e. without the -Wait option) " + "is to start the program and return without waiting for " + "the result code." ), - action='store_true', + action="store_true", ) runscript_argparser = Cmd2ArgumentParser() @@ -493,23 +482,23 @@ "-CommandLine", dest="command_line_args", help="Command line arguments", - nargs='?', + nargs="?", ) runscript_argparser.add_argument( "-Timeout", dest="script_timeout", help="Set timeout for the script (default: 30s)", - nargs='?', + nargs="?", default=30, type=int, ) shutdown_argparser = Cmd2ArgumentParser() shutdown_argparser.add_argument( - '-Confirm', - dest='confirm', - help='Confirms shutdown is ok', - action='store_true', + "-Confirm", + dest="confirm", + help="Confirms shutdown is ok", + action="store_true", ) tar_argparser = Cmd2ArgumentParser() @@ -518,121 +507,122 @@ help="Source to compress", ) tar_argparser.add_argument( - '-f', - '--filename', + "-f", + "--filename", help="Target tar filename. Relative or absolute.", nargs=1, - dest='filename', + dest="filename", ) tar_create_update_argparser = tar_argparser.add_mutually_exclusive_group(required=True) tar_create_update_argparser.add_argument( - '-c', - '--create', + "-c", + "--create", help="Create a new archive, and overwrite any other with the same name", - dest='create', - action='store_true', + dest="create", + action="store_true", ) tar_create_update_argparser.add_argument( - '-u', - '--update', + "-u", + "--update", help=( "Update an existing archive if one already exists, " "otherwise create a new archive (same as -c)" ), - dest='update', - action='store_true', + dest="update", + action="store_true", ) tar_compression_argparser = tar_argparser.add_mutually_exclusive_group(required=False) tar_compression_argparser.add_argument( - '-a', - '--auto', + "-a", + "--auto", help="Automatically decide on a compression method", - dest='compress_auto', - action='store_true', + dest="compress_auto", + action="store_true", ) tar_compression_argparser.add_argument( - '-z', - '--gzip', + "-z", + "--gzip", help="Gzip Compression", - dest='gzip', - action='store_true', + dest="gzip", + action="store_true", ) tar_compression_argparser.add_argument( - '-j', - '--bzip2', + "-j", + "--bzip2", help="Bzip2 Compression", - dest='bzip2', - action='store_true', + dest="bzip2", + action="store_true", ) tar_compression_argparser.add_argument( - '-J', - '--lzma', + "-J", + "--lzma", help="LZMA/XZ Compression", - dest='lzma', - action='store_true', + dest="lzma", + action="store_true", ) umount_argparser = Cmd2ArgumentParser() umount_argparser.add_argument( - 'filesystem', help='Filesystem to unmount', + "filesystem", + help="Filesystem to unmount", ) umount_argparser.add_argument( - '-f', - '--force', - dest='force_umount', - action='store_true', + "-f", + "--force", + dest="force_umount", + action="store_true", ) unmap_argparser = Cmd2ArgumentParser() unmap_argparser.add_argument( - 'drive_letter', + "drive_letter", help="Drive letter (with or without ':')", ) update_argparser = Cmd2ArgumentParser() update_subparsers: Cmd2ArgumentParser = update_argparser.add_subparsers( - title='Windows Update manipulation', - description='Choose how to work with Windows Update', - dest='command_name', + title="Windows Update manipulation", + description="Choose how to work with Windows Update", + dest="command_name", required=True, ) update_subparsers.add_parser( - 'history', - help='Use the Windows Update Agent to list update history', + "history", + help="Use the Windows Update Agent to list update history", ) update_install_subparser: Cmd2ArgumentParser = update_subparsers.add_parser( - 'install', + "install", help=( - 'Use the Windows Update Agent to download and install ' - 'the available update matching the input' - ) + "Use the Windows Update Agent to download and install " + "the available update matching the input" + ), ) update_install_subparser.add_argument( - 'kb', + "kb", help=( - 'A string containing one or more KB values. To install one KB, ' - 'just provide the number, e.g. update install 4565351. To install ' - 'multiple KBs, provide them delimited by spaces surrounded by double ' + "A string containing one or more KB values. To install one KB, " + "just provide the number, e.g. update install 4565351. To install " + "multiple KBs, provide them delimited by spaces surrounded by double " 'quotes, e.g. update install "4565351 4569751".' ), ) update_subparsers.add_parser( - 'list', - help='Use the Windows Update Agent to list available updates', + "list", + help="Use the Windows Update Agent to list available updates", ) update_query_subparser: Cmd2ArgumentParser = update_subparsers.add_parser( - 'query', + "query", help=( - 'Use the Windows Update Agent to list available updates matching ' - 'one or more KBs provided as an input' + "Use the Windows Update Agent to list available updates matching " + "one or more KBs provided as an input" ), ) update_query_subparser.add_argument( - 'kb', + "kb", help=( - 'A string containing one or more KB values. To query one KB, ' - 'just provide the number, e.g. update query 4565351. To query ' - 'multiple KBs, provide them delimited by spaces surrounded by double ' + "A string containing one or more KB values. To query one KB, " + "just provide the number, e.g. update query 4565351. To query " + "multiple KBs, provide them delimited by spaces surrounded by double " 'quotes, e.g. update query "4565351 4569751".' ), ) @@ -640,26 +630,23 @@ xmemdump_argparser = Cmd2ArgumentParser() xmemdump_argparser.add_argument( "mode", - choices=['complete', 'kerneldbg'], + choices=["complete", "kerneldbg"], type=str.lower, - help=( - 'Complete (complete host memory) or ' - 'KernelDbg (kernel memory with debug symbols)' - ), + help=("Complete (complete host memory) or KernelDbg (kernel memory with debug symbols)"), ) xmemdump_argparser.add_argument( - 'destination', - help='Target memdump file name. Absolute or relative path.', + "destination", + help="Target memdump file name. Absolute or relative path.", ) zip_argparser = Cmd2ArgumentParser() zip_argparser.add_argument( - 'source', - help='Source file or directory', + "source", + help="Source file or directory", ) zip_argparser.add_argument( - 'destination', - help='Target zip file name. Relative or absolute path.', + "destination", + help="Target zip file name. Relative or absolute path.", ) _PARSERS = { diff --git a/falcon_toolkit/shell/prompt.py b/falcon_toolkit/shell/prompt.py index 014a754..db5e64d 100644 --- a/falcon_toolkit/shell/prompt.py +++ b/falcon_toolkit/shell/prompt.py @@ -3,6 +3,7 @@ This file contains the bulk of the code that implements the RTR batch shell. It is configured, and in turn instantiated and invoked, by the code within shell/cli.py. """ + import concurrent.futures import csv import os @@ -102,7 +103,7 @@ def __init__( self.queueing = queueing self.add_settable( Settable( - 'queueing', + "queueing", bool, "RTR Command Queueing (True or False)", self, @@ -114,7 +115,7 @@ def __init__( self.timeout = timeout self.add_settable( Settable( - 'timeout', + "timeout", int, "RTR Command Timeout (seconds)", self, @@ -135,7 +136,7 @@ def _grab_put_files(): put_files = self.client.rtr.describe_put_files() put_file_names = [] for put_file_id in put_files.keys(): - put_file_names.append(put_files[put_file_id]['name']) + put_file_names.append(put_files[put_file_id]["name"]) PUT_FILE_CHOICES.clear() PUT_FILE_CHOICES.extend(sorted(put_file_names)) @@ -147,7 +148,7 @@ def _grab_custom_scripts(): custom_scripts = self.client.rtr.describe_scripts() script_names = [] for script_id in custom_scripts.keys(): - script_names.append(custom_scripts[script_id]['name']) + script_names.append(custom_scripts[script_id]["name"]) CLOUD_SCRIPT_CHOICES.clear() CLOUD_SCRIPT_CHOICES.extend(sorted(script_names)) @@ -185,19 +186,21 @@ def _grab_custom_scripts(): # Identify how many systems actually connected properly based on the 'complete' result self.successful_device_connections = sum( - ('complete' in x and x['complete'] is True) or - ('offline_queued' in x and x['offline_queued'] is True) + ("complete" in x and x["complete"] is True) + or ("offline_queued" in x and x["offline_queued"] is True) for x in self.connected_devices.values() ) # Bail out if no devices connected, since this shell will be useless if self.successful_device_connections == 0: - click.echo(click.style( - "No devices connected successfully. " - "If this is unexpected, please check the log file.", - fg="red", - bold=True, - )) + click.echo( + click.style( + "No devices connected successfully. " + "If this is unexpected, please check the log file.", + fg="red", + bold=True, + ) + ) sys.exit(1) # Figure out whether the prompt should show a *nix/macOS or Windows prompt, and set this up @@ -208,15 +211,15 @@ def _grab_custom_scripts(): self.csv_output_file = csv_output_file # pylint: disable=consider-using-with - self.csv_file_handle = open(csv_output_file, 'w', newline='', encoding='utf-8') + self.csv_file_handle = open(csv_output_file, "w", newline="", encoding="utf-8") fieldnames = [ - 'n', - 'command', - 'aid', - 'hostname', - 'complete', - 'stdout', - 'stderr', + "n", + "command", + "aid", + "hostname", + "complete", + "stdout", + "stderr", ] self.csv_writer = csv.DictWriter( self.csv_file_handle, @@ -250,20 +253,20 @@ def _derive_root_path(self) -> str: """ first_queued_root_path = None for device in self.connected_devices.values(): - if device['offline_queued']: + if device["offline_queued"]: # Queued (offline) hosts will not give us a prompt in stdout # Skip, and hope that another host is online if not first_queued_root_path: first_queued_root_path = "/" - device_id = device['aid'] - if self.device_data[device_id]['platform_name'] == 'Windows': + device_id = device["aid"] + if self.device_data[device_id]["platform_name"] == "Windows": first_queued_root_path = "C:\\" continue - if 'base_command' in device and device['base_command'] == 'pwd': + if "base_command" in device and device["base_command"] == "pwd": # On initial connection, the pwd pseudo-command will be run # to reset the path back to the root and echo it to stdout - stdout = device['stdout'] + stdout = device["stdout"] return stdout # Something weird has happened in this case, so just return an OS-dependant root @@ -277,8 +280,8 @@ def _derive_root_path(self) -> str: # If none of the devices have given us a useful value, just return an OS-dependent root path # The iteration trick avoids iterating over the whole dictionary again first_device = next(iter(self.connected_devices.items()))[1] - first_device_id = first_device['aid'] - if self.device_data[first_device_id]['platform_name'] == "Windows": + first_device_id = first_device["aid"] + if self.device_data[first_device_id]["platform_name"] == "Windows": return "C:\\" # If all else fails, just return a *nix / @@ -287,7 +290,7 @@ def _derive_root_path(self) -> str: def _set_prompt(self, prompt: str): """Set the prompt to a command-line style prompt, corrected based on the likely platform.""" prompt_character = ">" if ":\\" in prompt else " #" - self.prompt = f'{Style.DIM}{Fore.WHITE}{prompt}{prompt_character} {Style.RESET_ALL}' + self.prompt = f"{Style.DIM}{Fore.WHITE}{prompt}{prompt_character} {Style.RESET_ALL}" def _onchange_timeout(self, param_name, old, new): """Handle when the timeout parameter is changed.""" @@ -305,9 +308,7 @@ def _onchange_queueing(self, param_name, old, new): else: self.queueing = new self.batch_session.connect( - device_ids=self.device_ids, - queueing=self.queueing, - timeout=self.timeout + device_ids=self.device_ids, queueing=self.queueing, timeout=self.timeout ) root_path = self._derive_root_path() self._set_prompt(root_path) @@ -387,24 +388,17 @@ def _search_get_files( return get_file_data - def write_result_row( - self, - command: str, - aid: str, - complete: bool, - stdout: str, - stderr: str - ): + def write_result_row(self, command: str, aid: str, complete: bool, stdout: str, stderr: str): """Write a row of output to the CSV log file.""" hostname = self.device_data[aid].get("hostname", "") row = { - 'n': self.output_line_n, - 'command': command, - 'aid': aid, - 'hostname': hostname, - 'complete': complete, - 'stdout': stdout, - 'stderr': stderr, + "n": self.output_line_n, + "command": command, + "aid": aid, + "hostname": hostname, + "complete": complete, + "stdout": stdout, + "stderr": stderr, } self.csv_writer.writerow(row) self.output_line_n += 1 @@ -435,9 +429,9 @@ def send_generic_command(self, command: str) -> Tuple[Optional[str], Optional[st outputs: Optional[Tuple[Optional[str], Optional[str]]] = None error_msg_set = set() for aid, batch_result in batch_results.items(): - complete = batch_result['complete'] - stdout = batch_result['stdout'] - stderr = batch_result['stderr'] + complete = batch_result["complete"] + stdout = batch_result["stdout"] + stderr = batch_result["stderr"] self.write_result_row( command=command, aid=aid, @@ -453,28 +447,27 @@ def send_generic_command(self, command: str) -> Tuple[Optional[str], Optional[st if not printed_first: hostname = self.device_data[aid].get("hostname", "") - self.poutput(f'{hostname}: {stdout}') - self.perror(f'{Fore.RED}{hostname}: {stderr}{Fore.RESET}') + self.poutput(f"{hostname}: {stdout}") + self.perror(f"{Fore.RED}{hostname}: {stderr}{Fore.RESET}") printed_first = True - if 'errors' in batch_result and batch_result['errors']: - error_msg_set.add(batch_result['errors'][0]['message']) + if "errors" in batch_result and batch_result["errors"]: + error_msg_set.add(batch_result["errors"][0]["message"]) if batch_result_count > 1: self.poutput( - f'(Output from the remaining {batch_result_count - 1} ' - 'host(s) was written to the CSV output file)' + f"(Output from the remaining {batch_result_count - 1} " + "host(s) was written to the CSV output file)" ) if error_msg_set: self.poutput( - Fore.RED + - "At least one error was detected. Check the log file for full details." + Fore.RED + "At least one error was detected. Check the log file for full details." ) self.poutput(Fore.WHITE + "List of errors detected:") for err in error_msg_set: - self.poutput(f'-> {Style.DIM}{err}') + self.poutput(f"-> {Style.DIM}{err}") if outputs is None: return (None, None) @@ -488,15 +481,15 @@ def send_generic_command(self, command: str) -> Tuple[Optional[str], Optional[st def do_cat(self, args): """Read a file from disk and display as ASCII or hex.""" if args.show_hex: - command = f'cat {args.file} -ShowHex' + command = f"cat {args.file} -ShowHex" else: - command = f'cat {args.file}' + command = f"cat {args.file}" self.send_generic_command(command) @with_argparser(PARSERS.cd, preserve_quotes=True) def do_cd(self, args): """Change the current working directory.""" - command = f'cd {args.directory}' + command = f"cd {args.directory}" new_directory, _ = self.send_generic_command(command) # Handle the case when no valid directory is returned @@ -509,34 +502,31 @@ def do_cloud_scripts(self, args): scripts = self.client.rtr.query_scripts() sorted_scripts = sorted( scripts.items(), - key=lambda x: x[1]['name'], + key=lambda x: x[1]["name"], ) - if args.script_name: - found_script = False + found_script = False # Since we've made this API call, we might as well update the # choices for the parser, too. CLOUD_SCRIPT_CHOICES.clear() for script in sorted_scripts: script = script[1] - CLOUD_SCRIPT_CHOICES.append(script['name']) + CLOUD_SCRIPT_CHOICES.append(script["name"]) # Only show script information if the name matches # what was requested by the user if args.script_name: - if script['name'] == args.script_name: + if script["name"] == args.script_name: found_script = True else: continue - self.poutput( - Style.BRIGHT + Fore.BLUE + script['name'] + Style.RESET_ALL - ) + self.poutput(Style.BRIGHT + Fore.BLUE + script["name"] + Style.RESET_ALL) creator_length = max( - len(script['created_by']), - len(script['modified_by']), + len(script["created_by"]), + len(script["modified_by"]), ) self.poutput( f"{Style.BRIGHT}created by: {Fore.RED}" @@ -552,16 +542,13 @@ def do_cloud_scripts(self, args): ) self.poutput( - Style.BRIGHT + "Script length: " + Style.RESET_ALL + - str(script['size']) + " bytes" + Style.BRIGHT + "Script length: " + Style.RESET_ALL + str(script["size"]) + " bytes" ) - if 'description' in script: - self.poutput( - Style.RESET_ALL + script['description'] - ) + if "description" in script: + self.poutput(Style.RESET_ALL + script["description"]) if args.show_content: - self.poutput(Fore.CYAN + script['content'] + Style.RESET_ALL) + self.poutput(Fore.CYAN + script["content"] + Style.RESET_ALL) self.poutput() @@ -571,7 +558,7 @@ def do_cloud_scripts(self, args): @with_argparser(PARSERS.cp, preserve_quotes=True) def do_cp(self, args): """Copy a file or directory.""" - command = f'cp {args.source} {args.destination}' + command = f"cp {args.source} {args.destination}" self.send_generic_command(command) @with_argparser(PARSERS.csrutil, preserve_quotes=True) @@ -588,9 +575,9 @@ def do_cswindiag(self, args): def do_encrypt(self, args): """Encrypt a file with AES-256.""" if args.key: - command = f'encrypt {args.path} {args.key}' + command = f"encrypt {args.path} {args.key}" else: - command = f'encrypt {args.path}' + command = f"encrypt {args.path}" self.send_generic_command(command) @@ -603,31 +590,30 @@ def do_env(self, args): def do_eventlog(self, args): """[Windows] Inspect event logs. Subcommands: backup, export, list, view.""" if args.command_name == "backup": - command = f'eventlog backup {args.name} {args.filename}' + command = f"eventlog backup {args.name} {args.filename}" elif args.command_name == "export": - command = f'eventlog export {args.name} {args.filename}' + command = f"eventlog export {args.name} {args.filename}" elif args.command_name == "list": - command = 'eventlog list' + command = "eventlog list" elif args.command_name == "view": if args.source_name and not args.count: self.perror( - Fore.RED + - "You must specify an event count if you specify a " + Fore.RED + "You must specify an event count if you specify a " "source name. This is for RTR reasons." ) return - command = f'eventlog view {args.name}' + command = f"eventlog view {args.name}" if args.count: - command = f'{command} {args.count}' + command = f"{command} {args.count}" if args.source_name: - command = f'{command} {args.source_name}' + command = f"{command} {args.source_name}" self.send_generic_command(command) @with_argparser(PARSERS.filehash, preserve_quotes=True) def do_filehash(self, args): """Generate the MD5, SHA1, and SHA256 hashes of a file.""" - command = f'filehash {args.file}' + command = f"filehash {args.file}" self.send_generic_command(command) @with_argparser(PARSERS.get, preserve_quotes=True) @@ -661,13 +647,13 @@ def do_get(self, args): self.last_batch_get_completed_uploads = 0 for host_id, get_data in resources.items(): # If the command could be sent to the host, it is complete - complete = get_data['complete'] + complete = get_data["complete"] # stdout from a get shows the name of the file to be uploaded - stdout = get_data['stdout'] + stdout = get_data["stdout"] # stderr will show us if this failed, and why - stderr = get_data['stderr'] + stderr = get_data["stderr"] # See if the request was queued for the future - queued = get_data['offline_queued'] + queued = get_data["offline_queued"] # All successful requests print the filename to stdout and stderr is left empty. # We also only count a successful result if it is not queued. @@ -695,14 +681,13 @@ def do_get_status(self, args): get_file_data = self._search_get_files(self.last_batch_get_cmd_req_ids) else: self.poutput( - Fore.RED + - "You must execute a batch get command first, or supply a " + Fore.RED + "You must execute a batch get command first, or supply a " "batch get request ID" ) return if not get_file_data: - self.poutput(f'{Fore.YELLOW}No GET files in that batch have been uploaded.{Fore.RESET}') + self.poutput(f"{Fore.YELLOW}No GET files in that batch have been uploaded.{Fore.RESET}") @with_argparser(PARSERS.download, preserve_quotes=False) def do_download(self, args): @@ -717,16 +702,15 @@ def do_download(self, args): get_file_data = self._search_get_files(self.last_batch_get_cmd_req_ids) else: self.poutput( - Fore.RED + - "You must execute a batch get command first, or supply a " + Fore.RED + "You must execute a batch get command first, or supply a " "batch get request ID" ) return if not get_file_data: self.poutput( - f'{Fore.YELLOW}No GET files in that batch are available ' - f'for download yet.{Fore.RESET}' + f"{Fore.YELLOW}No GET files in that batch are available " + f"for download yet.{Fore.RESET}" ) return @@ -776,26 +760,26 @@ def do_ipconfig(self, args): @with_argparser(PARSERS.ls, preserve_quotes=True) def do_ls(self, args): """Display the contents of the specified path.""" - command = f'ls {args.directory}' + command = f"ls {args.directory}" if args.long_format: - command += ' -l' + command += " -l" if args.follow_symlinks: - command += ' -L' + command += " -L" if args.recursive: - command += ' -R' + command += " -R" if args.sort_time_modified: - command += ' -T' + command += " -T" self.send_generic_command(command) @with_argparser(PARSERS.kill, preserve_quotes=True) def do_kill(self, args): """Kill a process.""" - command = f'kill {args.pid}' + command = f"kill {args.pid}" self.send_generic_command(command) @with_argparser(PARSERS.map, preserve_quotes=True) @@ -808,16 +792,16 @@ def do_map(self, args): def do_memdump(self, args): """[Windows] Dump the memory of a process.""" if args.filename: - command = f'memdump {args.pid} {args.filename}' + command = f"memdump {args.pid} {args.filename}" else: - command = f'memdump {args.pid}' + command = f"memdump {args.pid}" self.send_generic_command(command) @with_argparser(PARSERS.mkdir, preserve_quotes=True) def do_mkdir(self, args): """Create a new directory.""" - command = f'mkdir {args.directory}' + command = f"mkdir {args.directory}" self.send_generic_command(command) @with_argparser(PARSERS.mount, preserve_quotes=True) @@ -828,7 +812,7 @@ def do_mount(self, args): @with_argparser(PARSERS.mv, preserve_quotes=True) def do_mv(self, args): """Move a file or directory.""" - command = f'mv {args.source} {args.destination}' + command = f"mv {args.source} {args.destination}" self.send_generic_command(command) @with_argparser(PARSERS.netstat, preserve_quotes=True) @@ -849,13 +833,13 @@ def do_ps(self, args): @with_argparser(PARSERS.put, preserve_quotes=True) def do_put(self, args): """Put a file from the CrowdStrike cloud onto the machine.""" - command = f'put {args.file}' + command = f"put {args.file}" self.send_generic_command(command) @with_argparser(PARSERS.put_and_run, preserve_quotes=True) def do_put_and_run(self, args): """[Windows] Download and immediately execute a file from the CrowdStrike Cloud.""" - command = f'put-and-run {args.file}' + command = f"put-and-run {args.file}" self.send_generic_command(command) @with_argparser(PARSERS.put_files, preserve_quotes=False) @@ -864,20 +848,18 @@ def do_put_files(self, args): put_files = self.client.rtr.describe_put_files() sorted_put_files = sorted( put_files.items(), - key=lambda x: x[1]['name'], + key=lambda x: x[1]["name"], ) # Since we've made this API call, we might as well update the # choices for the parser, too. PUT_FILE_CHOICES.clear() for put_file in sorted_put_files: put_file = put_file[1] - self.poutput( - Style.BRIGHT + Fore.BLUE + put_file['name'] + Style.RESET_ALL - ) - PUT_FILE_CHOICES.append(put_file['name']) + self.poutput(Style.BRIGHT + Fore.BLUE + put_file["name"] + Style.RESET_ALL) + PUT_FILE_CHOICES.append(put_file["name"]) creator_length = max( - len(put_file['created_by']), - len(put_file['modified_by']), + len(put_file["created_by"]), + len(put_file["modified_by"]), ) self.poutput( f"{Style.BRIGHT}created by: {Fore.RED}" @@ -893,13 +875,10 @@ def do_put_files(self, args): ) self.poutput( - Style.BRIGHT + "File Size: " + Style.RESET_ALL + - str(put_file['size']) + " bytes" + Style.BRIGHT + "File Size: " + Style.RESET_ALL + str(put_file["size"]) + " bytes" ) - if 'description' in put_file: - self.poutput( - Style.RESET_ALL + put_file['description'] - ) + if "description" in put_file: + self.poutput(Style.RESET_ALL + put_file["description"]) self.poutput() @with_argparser(PARSERS.reg, preserve_quotes=True) @@ -920,18 +899,16 @@ def do_restart(self, args): self.send_generic_command("restart -Confirm") else: self.poutput( - Fore.YELLOW + - "You must confirm a restart with -Confirm. " - "No action was taken." + Fore.YELLOW + "You must confirm a restart with -Confirm. No action was taken." ) @with_argparser(PARSERS.rm, preserve_quotes=True) def do_rm(self, args): """Remove (delete) a file or directory.""" if args.force: - command = f'rm {args.path} -Force' + command = f"rm {args.path} -Force" else: - command = f'rm {args.path}' + command = f"rm {args.path}" self.send_generic_command(command) @@ -941,10 +918,10 @@ def do_run(self, args): command = f'run "{args.executable}"' if args.command_line_args: - command = f'{command} -CommandLine=```{args.command_line_args}```' + command = f"{command} -CommandLine=```{args.command_line_args}```" if args.wait: - command = f'{command} -Wait' + command = f"{command} -Wait" self.send_generic_command(command) @@ -953,24 +930,18 @@ def do_runscript(self, args): """Run a PowerShell script.""" # Handle Cloud Files first if args.cloud_file: - command = f"runscript -CloudFile=\"{args.cloud_file}\"" + command = f'runscript -CloudFile="{args.cloud_file}"' elif args.host_path: - command = f"runscript -HostPath=\"{args.host_path}\"" + command = f'runscript -HostPath="{args.host_path}"' elif args.raw_script: command = f"runscript -Raw=```{args.raw_script}```" elif args.workstation_path: - if ( - os.path.exists(args.workstation_path) and - os.path.isfile(args.workstation_path) - ): - with open(args.workstation_path, 'rt', encoding='utf8') as script_file_handle: + if os.path.exists(args.workstation_path) and os.path.isfile(args.workstation_path): + with open(args.workstation_path, "rt", encoding="utf8") as script_file_handle: contents = script_file_handle.read() command = f"runscript -Raw=```{contents}```" else: - self.poutput( - f"{args.workstation_path} could not be found; " - "command aborted." - ) + self.poutput(f"{args.workstation_path} could not be found; command aborted.") return if args.command_line_args: @@ -988,9 +959,7 @@ def do_shutdown(self, args): self.send_generic_command("shutdown -Confirm") else: self.poutput( - Fore.YELLOW + - "You must confirm a shutdown with -Confirm. " - "No action was taken." + Fore.YELLOW + "You must confirm a shutdown with -Confirm. No action was taken." ) @with_argparser(PARSERS.tar, preserve_quotes=True) @@ -1019,20 +988,20 @@ def do_tar(self, args): @with_argparser(PARSERS.unmap, preserve_quotes=True) def do_unmap(self, args): """Unmap an SMB (network) share drive.""" - command = f'unmap {args.drive_letter}' + command = f"unmap {args.drive_letter}" self.send_generic_command(command) @with_argparser(PARSERS.update, preserve_quotes=True) def do_update(self, args): """[Windows] Windows update manipulation. Subcommands: history, install, list, query.""" if args.command_name == "history": - command = 'update history' + command = "update history" elif args.command_name == "install": - command = f'update install {args.kb}' + command = f"update install {args.kb}" elif args.command_name == "list": - command = 'update list' + command = "update list" elif args.command_name == "query": - command = f'update query {args.kb}' + command = f"update query {args.kb}" else: self.poutput("Incorrect mode specified") return @@ -1043,14 +1012,14 @@ def do_update(self, args): def do_xmemdump(self, args): """Dump the complete or kernel memory of the target systems.""" if args.destination: - command = f'xmemdump {args.mode} {args.destination}' + command = f"xmemdump {args.mode} {args.destination}" else: - command = f'xmemdump {args.mode}' + command = f"xmemdump {args.mode}" self.send_generic_command(command) @with_argparser(PARSERS.zip, preserve_quotes=True) def do_zip(self, args): """Compress a file or directory into a zip file.""" - command = f'zip {args.source} {args.destination}' + command = f"zip {args.source} {args.destination}" self.send_generic_command(command) diff --git a/falcon_toolkit/shell/refresh.py b/falcon_toolkit/shell/refresh.py index dbc35ef..8c8ae97 100644 --- a/falcon_toolkit/shell/refresh.py +++ b/falcon_toolkit/shell/refresh.py @@ -7,6 +7,7 @@ This allows users to step away from the keyboard while a long operation runs and return to a shell that has not timed out. """ + from threading import Timer from caracara.modules.rtr import RTRBatchSession diff --git a/falcon_toolkit/shell/utils.py b/falcon_toolkit/shell/utils.py index 9692c6c..8e820d6 100644 --- a/falcon_toolkit/shell/utils.py +++ b/falcon_toolkit/shell/utils.py @@ -2,6 +2,7 @@ This file contains helper functions for the main shell. """ + import os from caracara.modules.rtr import GetFile @@ -18,6 +19,6 @@ def output_file_name(get_file: GetFile, hostname: str): filename_noext, ext = os.path.splitext(filename) - final_filename = f'{filename_noext}_{hostname}_{get_file.device_id}_{get_file.sha256}{ext}' + final_filename = f"{filename_noext}_{hostname}_{get_file.device_id}_{get_file.sha256}{ext}" return final_filename diff --git a/poetry.lock b/poetry.lock index c333651..f764d23 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "astroid" -version = "3.1.0" +version = "3.2.2" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, - {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, + {file = "astroid-3.2.2-py3-none-any.whl", hash = "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0"}, + {file = "astroid-3.2.2.tar.gz", hash = "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94"}, ] [package.dependencies] @@ -35,18 +35,64 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "backports-tarfile" -version = "1.1.0" +version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" files = [ - {file = "backports.tarfile-1.1.0-py3-none-any.whl", hash = "sha256:b2f4df351db942d094db94588bbf2c6938697a5f190f44c934acc697da56008b"}, - {file = "backports_tarfile-1.1.0.tar.gz", hash = "sha256:91d59138ea401ee2a95e8b839c1e2f51f3e9ca76bdba8b6a29f8d773564686a8"}, + {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, + {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] +testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "brotli" @@ -209,13 +255,13 @@ files = [ [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -463,13 +509,13 @@ files = [ [[package]] name = "crowdstrike-falconpy" -version = "1.4.2" +version = "1.4.4" description = "The CrowdStrike Falcon SDK for Python 3" optional = false python-versions = ">=3.7" files = [ - {file = "crowdstrike-falconpy-1.4.2.tar.gz", hash = "sha256:4fb4030d83dcd294ec782eb976ea9b03a05807ff0819a8622f240c401954124f"}, - {file = "crowdstrike_falconpy-1.4.2-py3-none-any.whl", hash = "sha256:776a0fe9274f31ca4b3e174a9e7c2c6ca3afedf36e201a2ee030dd0aff230ea4"}, + {file = "crowdstrike_falconpy-1.4.4-py3-none-any.whl", hash = "sha256:f940cba595e8ff548fb74514faebd1bf019e576b7909d73ec25ed914a325a079"}, + {file = "crowdstrike_falconpy-1.4.4.tar.gz", hash = "sha256:c906ec5edb02a4cec07401087720cb1e3e4bad37ea1738c1199fafe53dc4f8c9"}, ] [package.dependencies] @@ -481,43 +527,43 @@ dev = ["bandit", "coverage", "flake8", "pydocstyle", "pylint", "pytest", "pytest [[package]] name = "cryptography" -version = "42.0.5" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -550,13 +596,13 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -564,18 +610,18 @@ test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -591,22 +637,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.0.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, + {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "inflate64" @@ -738,13 +784,13 @@ testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytes [[package]] name = "jaraco-functools" -version = "4.0.0" +version = "4.0.1" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.functools-4.0.0-py3-none-any.whl", hash = "sha256:daf276ddf234bea897ef14f43c4e1bf9eefeac7b7a82a4dd69228ac20acff68d"}, - {file = "jaraco.functools-4.0.0.tar.gz", hash = "sha256:c279cb24c93d694ef7270f970d499cab4d3813f4e08273f95398651a634f0925"}, + {file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"}, + {file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"}, ] [package.dependencies] @@ -752,7 +798,7 @@ more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["jaraco.classes", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jeepney" @@ -771,13 +817,13 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "25.2.0" +version = "25.2.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.2.0-py3-none-any.whl", hash = "sha256:19f17d40335444aab84b19a0d16a77ec0758a9c384e3446ae2ed8bd6d53b67a5"}, - {file = "keyring-25.2.0.tar.gz", hash = "sha256:7045f367268ce42dba44745050164b431e46f6e92f99ef2937dfadaef368d8cf"}, + {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"}, + {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"}, ] [package.dependencies] @@ -807,13 +853,13 @@ files = [ [[package]] name = "more-itertools" -version = "10.2.0" +version = "10.3.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, - {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, ] [[package]] @@ -832,26 +878,48 @@ check = ["check-manifest", "flake8", "flake8-black", "isort (>=5.0.3)", "pygment test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "hypothesis", "pyannotate", "pytest", "pytest-cov"] type = ["mypy", "mypy-extensions"] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" 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.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -876,13 +944,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.43" +version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, - {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] [package.dependencies] @@ -890,27 +958,28 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.8" +version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, ] [package.extras] @@ -918,13 +987,13 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "py7zr" -version = "0.21.0" +version = "0.21.1" description = "Pure python 7-zip library" optional = false python-versions = ">=3.7" files = [ - {file = "py7zr-0.21.0-py3-none-any.whl", hash = "sha256:ea6ded2e5c6d8539e3406cb3b0317192b32af59cff13eaf87702acc36a274da6"}, - {file = "py7zr-0.21.0.tar.gz", hash = "sha256:213a9cc46940fb8f63b4163643a8f5b36bbc798134746c3992d3bc6b14edab87"}, + {file = "py7zr-0.21.1-py3-none-any.whl", hash = "sha256:57e5be6fafaa417fe93fa9c81f7f01bb579d3cfe1631f535a3e641200ac87dc2"}, + {file = "py7zr-0.21.1.tar.gz", hash = "sha256:dede8ed8b7b32b3586ac476da3a482b69dd433229420bf0f62c495404b72c799"}, ] [package.dependencies] @@ -1002,13 +1071,13 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.0" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] [[package]] @@ -1093,17 +1162,17 @@ files = [ [[package]] name = "pylint" -version = "3.1.0" +version = "3.2.5" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, - {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, + {file = "pylint-3.2.5-py3-none-any.whl", hash = "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6"}, + {file = "pylint-3.2.5.tar.gz", hash = "sha256:e9b7171e242dcc6ebd0aaa7540481d1a72860748a0a7816b8fe6cf6c80a6fe7e"}, ] [package.dependencies] -astroid = ">=3.1.0,<=3.2.0-dev0" +astroid = ">=3.2.2,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -1123,12 +1192,12 @@ testutils = ["gitpython (>3)"] [[package]] name = "pyperclip" -version = "1.8.2" +version = "1.9.0" description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" optional = false python-versions = "*" files = [ - {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, + {file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"}, ] [[package]] @@ -1230,13 +1299,13 @@ files = [ [[package]] name = "pytest" -version = "8.2.0" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -1263,146 +1332,111 @@ files = [ [[package]] name = "pyzstd" -version = "0.15.10" +version = "0.16.0" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" files = [ - {file = "pyzstd-0.15.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ff57d2a67aaff47eb60c37c5476af2a5056319aa2a5cab4b276f78b764be785"}, - {file = "pyzstd-0.15.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8195b3fe2ed91db03f969c3c5cf6a9bcb3a961749caec94479e9e7a96d7af14f"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ea6a794b8a4e0590bea1599e724d4877d34640058caf5c0b3f916ded06fc3a1"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:deb41298fb0c916875413373e5eba162e39309d8730be736b3a206d9b73afebf"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0f1f590a66769caea6cf47c3c5d06d004f01659449595d4bc737a42d4670bf0"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dfd0d97ac231c08551543d61d123cf44bdd2fc20ba5ebce1fb0c466036be15a"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c20a4ee0706db5c9f403e4c459fc81145f48cb20fccc1a64acfb6143f1a7b1fb"}, - {file = "pyzstd-0.15.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7c4530ec6bf3a27c3cba229026fa3669e6e1832fdc37cf74c73b113f884cb2d"}, - {file = "pyzstd-0.15.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cccfb03f829ecde3ee126befb4fe827e2228a50e8d9eaa0275c4dd21a0e59765"}, - {file = "pyzstd-0.15.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e045dc688bb618cf2a2e33cb487c0d8af5e31a4eaec18c9399b34e2aed58777"}, - {file = "pyzstd-0.15.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a22c4b26fb6134ece45e875515961bc9c1777df775f3488800bea55a381df91e"}, - {file = "pyzstd-0.15.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0779ccd652113d75cd461fa6581149550b0da55e72b1a6e28e6f49e9978c2d41"}, - {file = "pyzstd-0.15.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:150a7c7004b9df00f7cbb6dad0bdbab5d77c8c54b0857b8b3e50beae2810a041"}, - {file = "pyzstd-0.15.10-cp310-cp310-win32.whl", hash = "sha256:50186fa41c72929d21727dcc6cb87bef0adf09df9e982325a0ae5bfd20a9456b"}, - {file = "pyzstd-0.15.10-cp310-cp310-win_amd64.whl", hash = "sha256:8e3dff90b6abc56331a886b5067c3b6bc3fad1b6df5766247197b699afd013b7"}, - {file = "pyzstd-0.15.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:51c644157319e2ca87521bd9694df7f651aac3273e015f35cdfb47cb8597ce37"}, - {file = "pyzstd-0.15.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85582502d3df1db8077475d88d0bf5f35b8ddb8abf9badd8625781024da5f346"}, - {file = "pyzstd-0.15.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adb895a0081a932132aa3a233cfeba682a07ac0dc36b062152f240406eecfba1"}, - {file = "pyzstd-0.15.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0822c9f4852f9f6b1458f8fbf011bb887cefba792b0c7c10659e2c9c997d1c9"}, - {file = "pyzstd-0.15.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6fd189c85f2efe94d3945dfbb85cdbc4e213757be75aa9a45e186625498ddf"}, - {file = "pyzstd-0.15.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d6f7fcfef9166670df4b6710d1105b891efba68ec523f56c64cc8c7aa6c8e88"}, - {file = "pyzstd-0.15.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527bfd3e01b8dafa1c24cba1a0455a85bbcdb41a3eefe59e2849634f9206d4f0"}, - {file = "pyzstd-0.15.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:330d54a0e5886fa5ca21e3960b9352a7b21f8dd85f98d4b7c4e8a7d48ade6998"}, - {file = "pyzstd-0.15.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a900a3dcda64b289cf35acfb6c90246b5ae9f2dd4bc05da8dcb8fbd04c91e37b"}, - {file = "pyzstd-0.15.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e21208605759dc2eeebf607a002e1ddc09548c12f25886d2a2dd8efdf35535a3"}, - {file = "pyzstd-0.15.10-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:015a6794196fcb75ddaaae8186f0dbdcbda4e84451361a808b3e96c8c8a63296"}, - {file = "pyzstd-0.15.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b856928e99a1d3080929b0f194c7922c7a7979b0eb21ad9d057f000dcf1a6ac7"}, - {file = "pyzstd-0.15.10-cp311-cp311-win32.whl", hash = "sha256:39a912430c73aa585ede2dc7c9f09c5334862953f26a9a86ac048c3805ed6bb1"}, - {file = "pyzstd-0.15.10-cp311-cp311-win_amd64.whl", hash = "sha256:4cd0bd21e0c7721e9f4ea54d533d9196771ab1d6ae0a83a96564dc46147a26eb"}, - {file = "pyzstd-0.15.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13515a6abca6751482396b656e5b72279bd405fd6c71b5bc899fcb526d40e564"}, - {file = "pyzstd-0.15.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62a1614558478f7d1014eec94a9ae3bf21c95766ecbe0a4a1ab16d2f1e4a6b67"}, - {file = "pyzstd-0.15.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d62a54dd438b51dd9f94bd88dd2f7c9d4093aadc44892143182d51f8488ab5f6"}, - {file = "pyzstd-0.15.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa20eb8f710a9d36801451d1cbcfc83d1dd9e400527ad9aba7e7cc635c2729b"}, - {file = "pyzstd-0.15.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3443df2dcdfe6e57c2600617e8f377772703b7f907fe065b49948c05ace80fd7"}, - {file = "pyzstd-0.15.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3d4e991ee727f6c0a54fe975ebe169be3a463ce810cfd8e6edbe8a90ddeb8f"}, - {file = "pyzstd-0.15.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53172f0781b3db9321a1286bb274121668c3fe560d3bddfa82f585475f7145fa"}, - {file = "pyzstd-0.15.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6217f30c4341619c6066a044ca118f53a3121b5813a56257b1f9818e24450584"}, - {file = "pyzstd-0.15.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cfa03e12c0c1bd357a0e1f20adae9c32bc166dcc6ed3be65885134830899b6ba"}, - {file = "pyzstd-0.15.10-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:455d2258f247968d18a9eaeb11e214e048acc976a61f72a0282d4c40051458b8"}, - {file = "pyzstd-0.15.10-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e0262bba03bb4d38b697c2039a8e48a9e889fb9ae9b727903e8d9c49d32883a4"}, - {file = "pyzstd-0.15.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7f7a398bcb0f4cfbaf0ddb3e831c43857701c63c66f48eb30011326ed03b0fa"}, - {file = "pyzstd-0.15.10-cp312-cp312-win32.whl", hash = "sha256:ce8e8a4aa8246c615693b9d0504e1df98157ae48907a044baa80f80abecc44ad"}, - {file = "pyzstd-0.15.10-cp312-cp312-win_amd64.whl", hash = "sha256:0561710b88d4895a7bb202335e802125da6a71c556b1292fd620224b2652f496"}, - {file = "pyzstd-0.15.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:62b8c3801d739a9592c0503dd294fab7f8cd2b1d637429babe13671bedf7d6f7"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2cc02e94ff4df6a4a871bbd69149ba5fbc5150108a208c6cdd9bc728000995b"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f174c47c370641c5dba74d4fd99374ca0eadd47e43e7f76ee8b76dacdfa2406"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1752344c77dbb137244e6ab5af5a7576b8e8b8e5d440c58d935e69f83606e0d"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94973e29acf813a572adf3ed2342a0998a41331781d7be82a0a1b02646181e34"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e71bb03c754934d7792aa0559b16f9effe1d4196f5834715c115363809f00c37"}, - {file = "pyzstd-0.15.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b19cdecdc19ad863673709d27ddb0adc104b9ad341f2785ab23c51c9ab39cf3f"}, - {file = "pyzstd-0.15.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6aed97ef51e20ea4fd7e12c4b9e3199cd8e4664054f5d84c3d83e6f4d3144055"}, - {file = "pyzstd-0.15.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4094ea7b80081bb024eb887c80eeb6bd8ae24dc2862cc61ae9eadb5bca11dcd6"}, - {file = "pyzstd-0.15.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:65e99e57f5fb42ed391fc2f7ffa4b8cce5a32dfd74f52e03bd36bb2ac3b56536"}, - {file = "pyzstd-0.15.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:957751dd7bba26e651c689fc5f41bb269712efcbf19a97ad4f912f0f792e6e95"}, - {file = "pyzstd-0.15.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ba0f1c0201845b769c92b2b3e7666010abe14d6e9dbf4214d04cdf23cbdcd1ae"}, - {file = "pyzstd-0.15.10-cp36-cp36m-win32.whl", hash = "sha256:ad30429f499c30e8e19ed389b8e25116d43c63cafdb97d4f5617e94e671209c5"}, - {file = "pyzstd-0.15.10-cp36-cp36m-win_amd64.whl", hash = "sha256:49c99120f9ea0c512019948e55ba17e60dadb1d3578890d53b2a09858da4e1dd"}, - {file = "pyzstd-0.15.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eb4d96fe5d6d8bf5c8c64d8a37edff55e6d9abbf84d9b8fd0fe8e0db1a557625"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ca483ee2084d36dc570004b6793f99c58d2df201c630f60518bca3518faa5d"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bb791f16c810c98865c373202a44b7b313d01a2a7a6186c9dcc0ac1a0d156dc"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d970caaaf3f59d6c7aa0aaf38e755641351f99452c400fc06b7e365ab1620c"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a15a3ef14cb929b07e0a98639f2ebcd69a0038d6e31367157018ec841a54d8fa"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e69d3d5257607a3b29f12192ad7bc9fdbdc8aa5f7e11c6f536cb61ee02dcd698"}, - {file = "pyzstd-0.15.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca80bb0b8400888e16e8eaa01b506de7d1bbdc8cc78c9ff75272526fd36fcb8a"}, - {file = "pyzstd-0.15.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99902df14861e4f40ed1eb403a17356188428d12bb10d53de10d1ea24996b838"}, - {file = "pyzstd-0.15.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:be591f405d82f40acbe2a081f1dee5478a3cf1b694013cc5712e84159311e36a"}, - {file = "pyzstd-0.15.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c867d446b22a7288229a812da6f211d86934beb47edf8098e2e0dbe9e2529f86"}, - {file = "pyzstd-0.15.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c6f67ac9d3ff65ca466821d573b3cb2fb01f0cd9eb082e92f49dfd88fa828a77"}, - {file = "pyzstd-0.15.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5ae5f8ab4accc980fa8e6696a402176e20a717ab2b296cd8d62b6a86cafc5f9c"}, - {file = "pyzstd-0.15.10-cp37-cp37m-win32.whl", hash = "sha256:013b206c22b85512d59dd05f6684c02bf9db45e93f637facb5ce165c2447fbc4"}, - {file = "pyzstd-0.15.10-cp37-cp37m-win_amd64.whl", hash = "sha256:357e6545203754e83a0431663aa86cae873abe160923bdf3db07e88f03100b05"}, - {file = "pyzstd-0.15.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9593a76f516312ec59dbeb73a9f1915d1f67610c054a7b488912b972cbfbcb7e"}, - {file = "pyzstd-0.15.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d55d9e1e22ebfa9cde606a7967eb21fbc65d0d3be600e550c38a1c05094c8015"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f356263a898b2c9961a2661ebb975f2fd20868d2c4fc4a165226fa7d36e1c76"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5494515f3d0df1eb54c9f4ce7b9ea0b285bc1ba87eba295604da5f8de711bdea"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dca0cde0f02e08e11431fff0e5408ac46420912165d828bc148a3c8cedc8852"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91e8a0852784c758b8de4179c61e80abae997155aa2b324cbee28dcaf9084b6f"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d62f357845f90d5327bdd3286ebd79c40b23b8e7c36772fc2949ef54a92bc6de"}, - {file = "pyzstd-0.15.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b2ac847e8d2ca55be038d29d20d6bfe86c03e66ac2d9741701e4f9eef2abda9"}, - {file = "pyzstd-0.15.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2d2a7324fbd118dc951b63b1fc553aec0fc0c430474a6ab7b33608b9606658a"}, - {file = "pyzstd-0.15.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:609cc4bed06942e51ebdfa9cc74629e1d19de2339161b65d555d01b9820aec46"}, - {file = "pyzstd-0.15.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b62c408ddac3ae113b9dc4867fd2bb32a2ef669c68f256eb5486c02b31603afa"}, - {file = "pyzstd-0.15.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:18167c8a16709631eaa2f4fd27e2a7570b4c37ec460b65ff098e948af2d37f33"}, - {file = "pyzstd-0.15.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:801ab496766a858cc54b37a66f9d0169d590628e69bde2166f76c35dfc20f64f"}, - {file = "pyzstd-0.15.10-cp38-cp38-win32.whl", hash = "sha256:846050f1d7520350d26076df12b8a78b540780d7d3478845be79d57e2e6d3612"}, - {file = "pyzstd-0.15.10-cp38-cp38-win_amd64.whl", hash = "sha256:d077f76179de41cdd5ae39b0a6f16f033b077f27fa54a9e038d03e3f5ddeff23"}, - {file = "pyzstd-0.15.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e2dbb36017e5c7d7499dfc05f8d6ed781fce9fbfa45cc7bd888ec5f871aba3d3"}, - {file = "pyzstd-0.15.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:41e0da402bf0d68067be2f16f65b9d1d7d7e6d726f27762226cba7a8badd618a"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62e98375385ade756d1ea4975f79ec103c515b412884d146c98295c5300fb4a"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8575d340ffcd89e4558448eec23c252da584f0de4528a73ed45a01f12d6700fd"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6bfb81f36fd56664dbbd64a0af241797424c76861faa2682aca89de110fce5d4"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965e04aeb83f594577ea4e8c975765d0953a75c8d6e3cd193ae25ef574bd83ee"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:793077870191d54889cc472eda3bfd84c8cae5ed61c9fa9fb2ce29c31e23f812"}, - {file = "pyzstd-0.15.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:468f12eb61cbef394ff412c86d8bc85b90f24dff19f675c864df88a1a398a3a3"}, - {file = "pyzstd-0.15.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:92abe38cdd7ea3f98a0f1a4521e9e7e70aa001713bff62a5b97fae41b0a39d0d"}, - {file = "pyzstd-0.15.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6fa6495ec52ff29b097e6d1b619588ab61e998b198882ec622d947856d4a85b4"}, - {file = "pyzstd-0.15.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:1c3d0e784ea9b66b06c7b76895aa4c1a90975980b1ae136fa8bbd8fb2b93e9b3"}, - {file = "pyzstd-0.15.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:bafef40c8b521ef9df81a118e278d5759c0127395883dcc10bf80ee32a46da39"}, - {file = "pyzstd-0.15.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81154db47a9aa901d09a8cebcd1f6559dbbd02a93a16bc203dc3f94c06bea25a"}, - {file = "pyzstd-0.15.10-cp39-cp39-win32.whl", hash = "sha256:6ed32af20eeeb7f8307b091fdd0ee8f5f8733dde7e8f560a3115c98a6ac33a61"}, - {file = "pyzstd-0.15.10-cp39-cp39-win_amd64.whl", hash = "sha256:8e4123de14a8dd7bccb1faed374c1f6c87111766279bbba5d6c4c52e39c383d9"}, - {file = "pyzstd-0.15.10-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0691e54fb29a4d1f4f2a0064afaf5d1525824bf139d83f403e5ba5ee630ac4f5"}, - {file = "pyzstd-0.15.10-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:656107ef6ff0966df2a58828f165a17b0b40977358512ade259c52c18bff0a13"}, - {file = "pyzstd-0.15.10-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea1fe83079c67c3f1549a735f11060350c87453bd86a8442bccb1f9b14c26aef"}, - {file = "pyzstd-0.15.10-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:502ec85cb2c0a35350a196e25d5e1350d400102706eba05d7926eadb64ffa976"}, - {file = "pyzstd-0.15.10-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a07742d42409c3ce98c58f2b155e2d8acdcd8ff49955062f5155dae94b24060a"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6023de5cc800c073cbf6a10a7aad0cbae0c1849c759bb5263a78d9ac774b167b"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63a90494aeea75d3298941b0b8ddd505cbb54d6b915e579368db21d93454e41d"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91da52897e5219dc440d3b9d290fdee66fb70c65ae594555c8d2c887df77f2de"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc917e488a9bdc681cb4a9b0ed3d0c8b81816bc1a6b1f85ec4050c827ffa9e76"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3b9105a4977247ea8664b113345e66bf4b3ffd321a07797efc6b020ff363b39e"}, - {file = "pyzstd-0.15.10-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:50079bcfd087c8a8822c9fe940c887244cf2276ca0a4f0b55bec006b564b9541"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3aa0ad903da666cf88d8a993e5fe9b1bfa0e5792a53212335af0a89baa268911"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5942e8b7ad869ea5f44d928053d3eda16e877b5fed5326786388f6a377545ab3"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d44c5cfe2516a652131ddd91c3a48924e34f4a7b85640cda44694afbeee6cb"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca3df18c269f9c11b40dde85336d3fc4a879867b05c74efdd4d5f8e64ac7e179"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a5b54ebec1e606821192c86a3b85c05c3f789d33bf44f383fd245f148f8f93b"}, - {file = "pyzstd-0.15.10-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6e742a691f4fa73cadbe7f866b33e33161ede5bc777eee443a62504adcadd65f"}, - {file = "pyzstd-0.15.10-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88e8cbd8fdd2af2f94615c32ab113c5cff4dbd188454394c4cac9941d2f72204"}, - {file = "pyzstd-0.15.10-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c309d50ee6a41053ad4c284295e5c5eedfd084ff94a5ee1148cf0dc64e3223fb"}, - {file = "pyzstd-0.15.10-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec319cdbba5ed3dfb2a8798364a8899751e732127c6ae74bc41276899e0311a"}, - {file = "pyzstd-0.15.10-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24d061047634971801649ff9aced68bb8403c6220b07c82aa2c0e90d8a61acd0"}, - {file = "pyzstd-0.15.10-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:330062103d25e0d67681fcf5ef8692c71cd979dc17ed98f6cee3b3ba8bd86622"}, - {file = "pyzstd-0.15.10.tar.gz", hash = "sha256:83603a97fdbcf2139f475c940789f09e32703f931f29f4a8ddf3551e6700108b"}, + {file = "pyzstd-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78f5e65eb15d93f687715be9241c8b55d838fba9b7045d83530f8831544f1413"}, + {file = "pyzstd-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:35962bc10480aebd5b32fa344430bddd19ef384286501c1c8092b6a9a1ee6a99"}, + {file = "pyzstd-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48037009be790fca505a62705a7997eef0cd384c3ef6c10a769734660245ee73"}, + {file = "pyzstd-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a57f2a0531ad2cd33bb78d8555e85a250877e555a68c0add6308ceeca8d84f1"}, + {file = "pyzstd-0.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa219d5d6124f1623b39f296a1fcc4cac1d8c82f137516bd362a38c16adcd92b"}, + {file = "pyzstd-0.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f560d24557bbc54eb1aa01ee6e587d4d199b785593462567ddf752de3c1c4974"}, + {file = "pyzstd-0.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14862ce066da0494e0f9466afc3b8fcd6c03f7250323cf8ef62c67158c77e57"}, + {file = "pyzstd-0.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5d0db66651ed5a866a1452e7a450e41a5ec743abbeea1f1bc85ef7c64f5f6b8f"}, + {file = "pyzstd-0.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f47aada7fdc6bcad8ec4ee4ff00a8d2d9a0e05b5516df3f304afbf527b026221"}, + {file = "pyzstd-0.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5c43e2222bbbe660dea8fe335f5c633b3c9ed10628a4b53a160ddd54d15cffc2"}, + {file = "pyzstd-0.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d897ec18822e348f8ef9a17e421716ed224a3726fde806aae04469fec8f0ac9d"}, + {file = "pyzstd-0.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d5c98986d774e9321fb1d4fe0362658560e14c1d7afbe2d298b89a24c2f7b4f"}, + {file = "pyzstd-0.16.0-cp310-cp310-win32.whl", hash = "sha256:84135917c99476c6abeee420ffd005a856d8fde0e5f585b0c484d5923392035b"}, + {file = "pyzstd-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:06b9dfd615fb5635c05153431e520954a0e81683c5a6e3ed1134f60cc45b80f1"}, + {file = "pyzstd-0.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c9c1ede5c4e35b059e8734dfa8d23a59b8fcfe3e0ece4f7d226bc5e1816512c9"}, + {file = "pyzstd-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75f4363157777cbcbbd14ff823388fddfca597d44c77c27473c4c4000d7a5c99"}, + {file = "pyzstd-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ff680078aec3b9515f149010981c7feeef6c2706987ac7bdc7cc1ea05f8f7d"}, + {file = "pyzstd-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbeaa0af865427405a1c0e8c65841a23de66af8ca5d796522f7b105386cd8522"}, + {file = "pyzstd-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f27e083a63b9463fd2640065af1b924f05831839f23d936a97c4f510a54f6b"}, + {file = "pyzstd-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dd4592c2fca923041c57aa2bfe428de14cc45f3a00ab825b353160994bc15e7"}, + {file = "pyzstd-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f22fb00bfcca4b2e0b36afd4f3a3194c1bc93b2a76e51932ccfd3b6aa62501"}, + {file = "pyzstd-0.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:586538aa2a992a55c10d88c58166e6023968a9825719bce5a09397b73eea658f"}, + {file = "pyzstd-0.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8e51d69446d96f5767e0f1b0676341d5d576c151dfe3dd14aff7a163db1b4d7c"}, + {file = "pyzstd-0.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8c675edd26cd2531163e51dcb3c7c73145e2fa3b77a1ff59ce9ed963ff56017"}, + {file = "pyzstd-0.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a765c5fc05fe1c843863cc3723e39e8207c28d9a7152ee6d621fa3908ef4880"}, + {file = "pyzstd-0.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79f4c9f1d7906eb890dafae4820f69bd24658297e9ebcdd74867330e8e7bf9b0"}, + {file = "pyzstd-0.16.0-cp311-cp311-win32.whl", hash = "sha256:6aa796663db6d1d01ebdcd80022de840005ae173e01a7b03b3934811b7ae39bc"}, + {file = "pyzstd-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a82cd4e772e5d1400502d68da7ecd71a6f1ff37243017f284bee3d2106a2496"}, + {file = "pyzstd-0.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e0f5a1865a00798a74d50fcc9956a3d7fa7413cbc1c6d6d04833d89f36e35226"}, + {file = "pyzstd-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00954290d6d46ab13535becbbc1327c56f0a9c5d7b7cf967e6587c1395cade42"}, + {file = "pyzstd-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:796a29cbb6414b6cb84d8e7448262ba286847b946de9a149dec97469a4789552"}, + {file = "pyzstd-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c68761529a43358151ac507aeb9c6b7c1a990235ce7b7d41f8ea62c62d4679e"}, + {file = "pyzstd-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8436ce4fa7e7ddaa8d29717fd73e0699883ef6e78ef4d785c244779a7ad1942b"}, + {file = "pyzstd-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:349d643aeb8d7d9e0a407cef29d6210afbe646cc19b4e237456e585591eda223"}, + {file = "pyzstd-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4cf0fed2d5c9de3da211dceff3ed9a09b8f998f7df57da847145863a786454b"}, + {file = "pyzstd-0.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:691cadd48f225097a2588e7db492ac88c669c061208749bc0200ee39e4425e32"}, + {file = "pyzstd-0.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:33efaf2cc4efd2b100699d953cd70b5a54c3ca912297211fda01875f4636f655"}, + {file = "pyzstd-0.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b3cc09eecd318310cfd6e7f245248cf16ca014ea5903580d72231d93330952de"}, + {file = "pyzstd-0.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89187af1ca5a9b65c477817e0fe7e411f4edd99e5575aaaef6a9e5ff62028854"}, + {file = "pyzstd-0.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7d5888e206190d36fbffed6d7e9cacd79e64fd34e9a07359e16862973d90b33"}, + {file = "pyzstd-0.16.0-cp312-cp312-win32.whl", hash = "sha256:3c5f28a145677431347772b43a9604b67691b16e233ec7a92fc77fc5fb670608"}, + {file = "pyzstd-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a2d5a8b74db3df772bb4f230319241e73629b04cb777b22f9dcd2084d92977a"}, + {file = "pyzstd-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:94fe8c5f1f11397b5db8b1850168e5bed13b3f3e1bc36e4292819d85be51a63c"}, + {file = "pyzstd-0.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d1e6ae36c717abd32b55a275d7fbf9041b6de3a103639739ec3e8c8283773fb3"}, + {file = "pyzstd-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33bc6f6048f7f7fc506e6ad03fb822a78c2b8209e73b2eddc69d3d6767d0385c"}, + {file = "pyzstd-0.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c4cdb0e407bec2f3ece10275449822575f6634727ee1a18e87c5e5a7b565bb1"}, + {file = "pyzstd-0.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e4cf6d11427d43734e8cb246ecfb7af169983ef796b415379602ea0605f5116"}, + {file = "pyzstd-0.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c0bbdb3ae1c300941c1f89219a8d09d142ddb7bfc78e61da80c8bdc03c05be8"}, + {file = "pyzstd-0.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34c06a6496b4aacdab03133671dd5638417bda09a1f186ba1a39c1dbd1add24"}, + {file = "pyzstd-0.16.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:29ca6db3fb72d17bcec091b9ba485c715f63ca00bfcd993f92cb20037ae98b25"}, + {file = "pyzstd-0.16.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:26e42ccb76a53c1b943021eeb0eb4d78f46093c16e4e658a7204c838d5b36df0"}, + {file = "pyzstd-0.16.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:76697baa4d9fd621bd5b99719d3b55fadeb665af9a49523debfc9ae5fbefef13"}, + {file = "pyzstd-0.16.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:708c442f8f6540ffad24a894bdea3c019250e02dcdbd0fbd27fc977b1a88b4f2"}, + {file = "pyzstd-0.16.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:994a21a75d7b2602a78c2f88f809427ce1051e43af7aad6cda524ccdc859354e"}, + {file = "pyzstd-0.16.0-cp38-cp38-win32.whl", hash = "sha256:80962ff81a3389b5579d1206bea1bb48da38991407442d2a9287f6da1ccb2c80"}, + {file = "pyzstd-0.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:363c11a4d60fa0e2e7437f7494291c24eaf2752c8d8e3adf8f92cb0168073464"}, + {file = "pyzstd-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:094cec5425097ae1f9a40bb02de917d2274bfa872665fe2e5b4101ee94d8b31d"}, + {file = "pyzstd-0.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca9f1f6bd487c9b990e509c17e0a701f554db9e77bd5121c27f1db4594ac4c0a"}, + {file = "pyzstd-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff99a11dd76aec5a5234c1158d6b8dacb61b208f3f30a2bf7ae3b23243190581"}, + {file = "pyzstd-0.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2820b607be0346b3e24b097d759393bd4bcccc0620e8e825591061a2c3a0add5"}, + {file = "pyzstd-0.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef883837c16c076f11da37323f589779806073eeacaef3912f2da0359cb8c2cf"}, + {file = "pyzstd-0.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c3181a462cdb55df5ddeffe3cf5223cda36c81feceeb231688af08d30f11022"}, + {file = "pyzstd-0.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80741b9f18149264acb639287347cfc6eecff109b5c6d95dbf7222756b107b57"}, + {file = "pyzstd-0.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fb70083bf00426194a85d69939c52b1759462873bf6e4d62f481e2bc3e642ea1"}, + {file = "pyzstd-0.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:44f818ea8c191285365a0add6fc03f88225f1fdcff570dc78e9f548444042441"}, + {file = "pyzstd-0.16.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:983ea93ed937d329c88ef15d5e3b09e32372590c1a80586b2013f17aed436cb8"}, + {file = "pyzstd-0.16.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0eadba403ec861fa4c600ad43dbd8ac17b7c22a796d3bd9d92918f4e8a15a6e8"}, + {file = "pyzstd-0.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a4e12b6702481ace7071357c1b81b9faf6f660da55ff9ccd6383fed474348cc6"}, + {file = "pyzstd-0.16.0-cp39-cp39-win32.whl", hash = "sha256:bc5e630db572362aef4d8a78f82a40e2b9756de7622feb07031bd400a696ad78"}, + {file = "pyzstd-0.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ef9fa7fe28dd6b7d09b8be89aea4e8f2d18b23a89294f51aa48dbc6c306a039"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b8db95f23d928ba87297afe6d4fff21bbb1af343147ff50c174674312afc29d"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3f661848fa1984f3b17da676c88ccd08d8c3fab5501a1d1c8ac5abece48566f2"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acfe529ff44d379ee889f03c2d353f94b1f16c83a92852061f9672982a3ef32d"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:493edd702bc16dae1f4d76461688714c488af1b33f5b3a77c1a86d5c81240f9e"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10143cad228ebeb9eda7793995b2d0b3fef0685258d9b794f6320824302c47d7"}, + {file = "pyzstd-0.16.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:784f7f87ae2e25459ef78282fbe9f0d2fec9ced84e4acb5d28621a0db274a13b"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35ba0ee9d6d502da2bc01d78d22f51a1812ff8d55fb444447f7782f5ce8c1e35"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:e8eae552db2aa587c986f460915786bf9058a88d831d562cadba01f3069736a9"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e31e0d2023b693ca530d95df7cff8d736f66b755018398bc518160f91e80bd0a"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0fa1ef68839d99b0c0d66fe060303f7f2916f021289a7e04a818ef9461bbbe1"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a55aac43a685b7d2b9e7c4f9f3768ad6e0d5f9ad7698b8bf9124fbeb814d43"}, + {file = "pyzstd-0.16.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:20259fa302f1050bd02d78d93db78870bed385c6d3d299990fe806095426869f"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bd27ab78269148c65d988a6b26471d621d4cc6eed6b92462b7f8850162e5c4f2"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5d8a3263b7e23a3593eb4fcc5cc77e053c7d15c874db16ce6ee8b4d94f8d825"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75f5e862e1646f1688e97f4aa69988d6589a1e036f081e98a3f202fa4647e69b"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19deddb2975af861320fd7b68196fbb2a4a8500897354919baf693702786e349"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48b4368b832233205a74e9f1dfe2647d9bc49ea8357b09963fd5f15062bdd0a"}, + {file = "pyzstd-0.16.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:74521d819ceea90794aded974cc3024c65c094050e6c4a6f4b7478af3461e3ad"}, + {file = "pyzstd-0.16.0.tar.gz", hash = "sha256:fd43a0ae38ae15223fb1057729001829c3336e90f4acf04cf12ebdec33346658"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1506,35 +1540,35 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.4" +version = "0.12.5" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, - {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, + {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, + {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -1556,20 +1590,20 @@ files = [ [[package]] name = "zipp" -version = "3.18.1" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "d17a3896a5ac62d923eab4e6a83406e1402032108ab5d937e98a50facd46bf8a" +content-hash = "aa3a25bccaafdb1e96f25d4c1bd94427ea796e09058d059cb991ba824cf3aaa7" diff --git a/pyproject.toml b/pyproject.toml index 7392a0d..09ea81c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ pylint = "^3.0.0" pytest = "^8.0.1" pydocstyle = "^6.1.1" toml = "^0.10.2" +black = "^24.4.2" [tool.pylint.messages_control] disable = [