From a0878f1485def73ce5a5a68fd38bac44bee67c3c Mon Sep 17 00:00:00 2001 From: Ryan Gerstenkorn Date: Tue, 16 Feb 2021 02:40:02 -0600 Subject: [PATCH] Develop (#9) * Refactor and cleanup * Add more tests * Add create and delete commands * Add help output * Update readme and other fixes --- .dockerignore | 6 + .gitignore | 2 +- Makefile | 2 +- README.md | 85 +- dsnap/__main__.py | 2 +- dsnap/{templates => files}/Vagrantfile | 0 dsnap/main.py | 154 +++- dsnap/prompt.py | 147 +-- dsnap/snapshot.py | 174 ++-- dsnap/utils.py | 7 +- events/snapshot-created.json | 22 + poetry.lock | 1139 ++++++++++++++++-------- pyproject.toml | 5 +- scan.sh | 5 + tests/test_aws.py | 3 +- tests/test_prompt.py | 16 +- tests/test_snapshot.py | 61 +- 17 files changed, 1223 insertions(+), 607 deletions(-) create mode 100644 .dockerignore rename dsnap/{templates => files}/Vagrantfile (100%) create mode 100644 events/snapshot-created.json create mode 100755 scan.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a1c847d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.venv +venv +.vagrant +vagrant +.mypy_cache +.git diff --git a/.gitignore b/.gitignore index d831588..d7207f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ .vscode .idea *.sw? -*.img *.vdi stubs tmp venv dist __pycache__ +samconfig.toml diff --git a/Makefile b/Makefile index c13a20d..871abc7 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ docker/build: docker build -f Dockerfile.mount -t dsnap-mount . docker/run: - docker run -it -v "${PWD}/${IMAGE}:/disks/${IMAGE}" -w /disks mount --ro -a "${IMAGE}" -m /dev/sda1:/ + docker run -it -v "${PWD}/${IMAGE}:/disks/${IMAGE}" -w /disks dsnap-mount --ro -a "${IMAGE}" -m /dev/sda1:/ test: pytest ./tests diff --git a/README.md b/README.md index ab8541a..30804cc 100644 --- a/README.md +++ b/README.md @@ -8,29 +8,62 @@ Utility for downloading EBS snapshots using the EBS Direct API's. ### PyPi +```shell +======= ``` % pip install -U pip % pip install 'dsnap[cli]' ``` +## Command Reference + +```shell +% dsnap --help +Usage: dsnap [OPTIONS] COMMAND [ARGS]... + + A utility for managing snapshots via the EBS Direct API. + +Options: + --region REGION Sets the AWS region. [default: us-east-1] + --profile PROFILE Shared credential profile to use. + --install-completion [bash|zsh|fish|powershell|pwsh] + Install completion for the specified shell. + --show-completion [bash|zsh|fish|powershell|pwsh] + Show completion for the specified shell, to + copy it or customize the installation. + + --help Show this message and exit. + +Commands: + create Create a snapshot for the given instances default device volume. + delete Delete a given snapshot. + get Download a snapshot for a given instance or snapshot ID. + init Write out a Vagrantfile template to explore downloaded snapshots. + list List snapshots in AWS. +``` + ## Examples +### Recording + +[![asciicast](https://asciinema.org/a/391559.svg)](https://asciinema.org/a/391559) + ### Listing Snapshots -``` -% dsnap --profile demo list +```shell +% dsnap list Id | Owner ID | State snap-0dbb0347f47e38b96 922105094392 completed ``` ### Downloading a Snapshot -``` -% dsnap --profile demo get snap-0dbb0347f47e38b96 +```shell +% dsnap get snap-0dbb0347f47e38b96 Output Path: /cwd/snap-0dbb0347f47e38b96.img ``` If you don't specify a snapshot you'll get a prompt to ask which one you want to download: -``` -% python -m dsnap --profile chris get +```shell +% dsnap chris get 0) i-01f0841393cd39f06 (ip-172-31-27-0.ec2.internal, vpc-04a91864355539a41, subnet-0e56cd55282fa9158) Select Instance: 0 0) vol-0a1aab48b0bc3039d (/dev/sdb) @@ -46,7 +79,7 @@ Cleaning up snapshot: snap-0543a8681adce0086 ### Mounting in Vagrant This requires virtualbox to be installed. dsnap init will write a Vagrantfile to the current directory that can be used to mount a specific downloaded snapshot. Conversion to a VDI disk is handled in the Vagrantfile, it will look for the disk file specified in the IMAGE environment variable, convert it to a VDI using `VBoxManage convertdd`. The resulting VDI is destroyed when the Vagrant box is, however the original raw .img file will remain and can be reused as needed. -``` +```shell % dsnap init % IMAGE=snap-0543a8681adce0086.img vagrant up % vagrant ssh @@ -57,16 +90,16 @@ This requires virtualbox to be installed. dsnap init will write a Vagrantfile to This uses libguestfs to work directly with the downloaded img file. #### Build Docker Container -``` -git clone https://github.com/RhinoSecurityLabs/dsnap.git -cd dsnap -make docker/build +```shell +% git clone https://github.com/RhinoSecurityLabs/dsnap.git +% cd dsnap +% make docker/build ``` #### Run Guestfish Shell -``` -IMAGE=snap-0dbb0347f47e38b96.img make docker/run +```shell +% IMAGE=snap-0dbb0347f47e38b96.img make docker/run ``` This will take a second to start up. After it drops you into the shell you should be able to run commands like ls, cd, cat. However worth noting they don't always behave exactly like they do in a normal shell. @@ -75,7 +108,7 @@ The output will give you the basics of how to use the guestfish shell. For a ful Below is an example of starting the shell and printing the contents of /etc/os-release. -``` +```shell % IMAGE=snap-0dbb0347f47e38b96.img make docker/run docker run -it -v "/cwd/dsnap/snap-0dbb0347f47e38b96.img:/disks/snap-0dbb0347f47e38b96.img" -w /disks mount --ro -a "snap-0dbb0347f47e38b96.img" -m /dev/sda1:/ @@ -104,26 +137,26 @@ HOME_URL="https://amazonlinux.com/" For CLI development make sure you include the `cli` extra shown below. You'll also want to invoke the package by using python's `-m` (shown below) for testing local changes, the dnsap binary installed to the environment will only update when you run pip install. ### Setup -``` -git clone https://github.com/RhinoSecurityLabs/dsnap.git -cd dsnap -python3 -m venv venv -. venv/bin/activate -python -m pip install '.[cli]' +```shell +% git clone https://github.com/RhinoSecurityLabs/dsnap.git +% cd dsnap +% python3 -m venv venv +% . venv/bin/activate +% python -m pip install '.[cli]' ``` ### Running With Local Changes -``` -python -m dsnap --help +```shell +% python -m dsnap --help ``` ### Linting and Type Checking -``` -make lint +```shell +% make lint ``` ### Testing -``` -make test +```shell +% make test ``` diff --git a/dsnap/__main__.py b/dsnap/__main__.py index ed9e1da..67be9eb 100644 --- a/dsnap/__main__.py +++ b/dsnap/__main__.py @@ -6,6 +6,6 @@ logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.WARNING) try: - app() + app(prog_name="dsnap") except (NoCredentialsError, NoRegionError) as e: logging.error(e.args[0]) diff --git a/dsnap/templates/Vagrantfile b/dsnap/files/Vagrantfile similarity index 100% rename from dsnap/templates/Vagrantfile rename to dsnap/files/Vagrantfile diff --git a/dsnap/main.py b/dsnap/main.py index 872e4f9..a86b962 100644 --- a/dsnap/main.py +++ b/dsnap/main.py @@ -1,20 +1,18 @@ -import logging - -import sys from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, List import boto3 -from typer import Argument, Option, Typer +import typer +from typer import Option, Typer from dsnap import utils -from dsnap.snapshot import Snapshot -from dsnap.prompt import snap_id_from_input +from dsnap.prompt import snap_from_input, download_snap_id, snaps_from_input, take_snapshot, vol_from_id +from dsnap.utils import fatal if TYPE_CHECKING: from mypy_boto3_ec2 import service_resource as r -app = Typer() +app = Typer(name="dsnap", help="Utility for downloading EBS snapshots using the EBS Direct API's.") sess: boto3.session.Session = boto3.session.Session() @@ -23,54 +21,132 @@ @app.callback() -def session(region: str = Option(default='us-east-1'), profile: str = Option(default=None)): - """This is function set's up various global settings. - - It is called by Typer before any of the other commands run due to the @app.callback decorator. - """ +def session( + region: str = Option(default='us-east-1', help="Sets the AWS region.", metavar="REGION"), + profile: str = Option(default=None, help="Shared credential profile to use.", metavar="PROFILE") +): global sess, ec2 sess = boto3.session.Session(region_name=region, profile_name=profile) ec2 = sess.resource('ec2') @app.command() -def init(out_dir: Path = Path('.'), force: bool = False): +def init( + out_dir: Path = typer.Option(Path('.'), help='Output directory to write Vagrantfile'), + force: bool = typer.Option(False, help='Overwrite any existing Vagrantfile.') +): + """ + Write out a Vagrantfile template to explore downloaded snapshots. + + If --out-dir is used the given directory will be used instead. + If --force is used we will overwrite an already present Vagrantfile + + To use the outputed Vagrantfile set the IMAGE environment to the path of the snapshot you want to mount and run vagrant + up. For example: + + % dsnap init + % IMAGE=snap-0543a8681adce0086.img vagrant up + % vagrant ssh + """ output = utils.init_vagrant(out_dir, force) if output: - print(f"Wrote Vagrantfile to {output}") + print(f"Wrote Vagrantfile to {typer.style('./'+str(output), bold=True)}") else: print(f"Vagrantfile already exists at {output}, use the --force to overwrite.") @app.command("list") -def list_snapshots(): - print(" Id | Owneer ID | Description") - ec2: 'r.EC2ServiceResource' = sess.resource('ec2') - for snap in ec2.snapshots.filter(OwnerIds=['self']).all(): - print(f"{snap.id} {snap.owner_id} {snap.description}") +def list_snapshots( + instance_id: str = typer.Argument(None, help='Optional instance ID to limit listed snapshots to.'), + devices: List[str] = typer.Option(['/dev/sda', '/dev/xvda'], help='Optional device name to limit snapshots to.'), +): + """ + List snapshots in AWS. + + If --instance-id is used then snapshots will be limited to that instances default device attachments. + If --devices is used alongside --instance-id then listed snapshots are for that instances given devices, by default this + is /dev/sda and /dev/xvda. + """ + print(typer.style(" Id | Owneer ID | Description ", underline=True)) + try: + for snap in snaps_from_input(sess, instance_id, devices): + print(f"{typer.style(snap.id, bold=True)} {snap.owner_id} {snap.description}") + except UserWarning as e: + fatal(*e.args) @app.command() -def get(id: str = Argument(None), output: Optional[Path] = None): - # snap_id will be None in cases of invalid argument or no snapshot was selected +def get( + # We use the filename to determine the snapshot id so we can only use directories for the output option. + output: Path = typer.Option( + Path('.'), + file_okay=False, + dir_okay=True, + help='If specified output the snapshot to the given directory, the name however is always the snapshot id.', + ), + force: bool = typer.Option(False, help='If specified and the snapshot already exists then overwrite it.'), + ids: Optional[List[str]] = typer.Argument(default=None, help='The remote snapshot ID to fetch.') +): + """ + Download a snapshot for a given instance or snapshot ID. + + If no Argument is passed then you'll be prompted to select an instance, volume and snapshot to download. If no snapshot + exists, you can optionally create a temporary one. + + If an instance ID is passed a snapshot for that instance will be downloaded, if more then one exists you'll be prompted + to select a one. + + If a snapshot ID is passed that snapshot will be downloaded and you will not be prompted for any additional info. + """ try: - snap_id = snap_id_from_input(sess, id) - except UserWarning as e: - print(*e.args, '\nExiting...') - sys.exit(1) + if not ids: + snap = snap_from_input(sess, ids) + download_snap_id(sess, force, output, snap.id) + else: + for id in ids: + snap = snap_from_input(sess, id) + download_snap_id(sess, force, output, snap.id) + except (UserWarning, FileExistsError) as e: + fatal(*e.args) + +@app.command() +def create(ids: List[str] = typer.Argument( + None, + help='One or more ID\'s of a instance or volume to create a snapshot for. To avoid being prompted use an explict volume ID' + ' rather then an instance ID.' +)): + """ + Create a snapshot for the given instances default device volume. + + The passed argument should be an instance ID, where a snapshot will be created from the default device volume, either + /dev/sda or /dev/xvda. + """ try: - logging.info(f"Selected snapshot with id {snap_id}") - snap = Snapshot(snap_id, boto3_session=sess) - path = output and output.absolute().as_posix() - snap.download(path or f"{snap.snapshot_id}.img") + if not ids: + fatal("must pass at least one instance or volume id as an argument") + for i in ids: + vol = vol_from_id(sess, i) + s = take_snapshot(vol) + print(f"Created snapshot {typer.style(s.id, bold=True)} from instance " f"{typer.style(i, bold=True)}") + except UserWarning as e: - print(*e.args) - sys.exit(2) - except Exception as e: - resp = getattr(e, 'response', None) - if resp and resp['Error']['Message']: - print(resp['Error']['Message']) - sys.exit(1) - else: - raise e + fatal(*e.args) + + +@app.command() +def delete(ids: List[str] = typer.Argument(None, help='One or more ID\'s of snapshots to delete')): + """ + Delete a given snapshot. + + The passed argument should be a snapshot ID to delete. + """ + if not ids: + fatal("must pass at least one instance id as an argument") + for i in ids: + try: + s = ec2.Snapshot(i) + s.delete() + print(f"Deleted snapshot {typer.style(s.id, bold=True)}") + except UserWarning as e: + fatal(*e.args) diff --git a/dsnap/prompt.py b/dsnap/prompt.py index 89899d9..7bd65f4 100644 --- a/dsnap/prompt.py +++ b/dsnap/prompt.py @@ -1,53 +1,101 @@ import json import sys -from typing import Optional, cast, List, TYPE_CHECKING +from typing import Optional, cast, TYPE_CHECKING, TypeVar, Iterable -import boto3 import jmespath -from boto3.resources.base import ServiceResource from boto3.resources.collection import ResourceCollection -from dsnap.utils import get_name_tag, create_tmp_snap +from dsnap.snapshot import LocalSnapshot +from dsnap.utils import get_name_tag, create_tmp_snap, fatal if TYPE_CHECKING: from mypy_boto3_ec2 import service_resource as r -def snap_id_from_input(sess, id) -> str: +def snaps_from_input(sess, id, devices): ec2: 'r.EC2ServiceResource' = sess.resource('ec2') if not id: - snap_id = full_prompt(sess) + for snap in ec2.snapshots.filter(OwnerIds=['self']).all(): + yield snap + elif id.startswith("i-"): + i: 'r.Instance' = ec2.Instance(id) + for d in i.block_device_mappings: + vol = ec2.Volume(d['Ebs']['VolumeId']) + for snap in vol.snapshots.all(): + yield snap + else: + raise UserWarning(f"Unexpected argument format: {id}, use an instance id or omit the argument to list all snapshots") + + +def snap_from_input(sess, id) -> 'r.Snapshot': + """download_from_id is meant to be called from the cli commands and will exit in the case of an error""" + ec2: 'r.EC2ServiceResource' = sess.resource('ec2') + vol: Optional['r.Volume'] = None + + if not id: + inst: 'r.Instance' = resource_prompt(ec2.instances.all(), '[PrivateDnsName, VpcId, SubnetId]') + vol = resource_prompt(inst.volumes.all(), 'Attachments[*].Device') + try: + snap = resource_prompt(cast('r.Volume', vol).snapshots.all(), '[StartTime, OwnerId, Description]') + except UserWarning: + snap = ask_to_create_snapshot(vol) elif id.startswith('snap-'): - snap_id = id + snap = ec2.Snapshot(id) elif id.startswith('i-'): - vol = volume_prompt(ec2.Instance(id).volumes) - snap_id = (snapshot_prompt(vol.snapshots) or ask_to_create_snapshot(vol)).snapshot_id + vol = resource_prompt(ec2.Instance(id).volumes.all(), 'Attachments[*].Device') + try: + snap = resource_prompt(cast('r.Volume', vol).snapshots.all(), '[StartTime, OwnerId, Description]') + except UserWarning: + snap = ask_to_create_snapshot(vol) else: - raise UserWarning("unknown argument type, first argument should be an Instance Id or Snapshot Id") - return snap_id + raise UserWarning('unknown argument type, first argument should be an Instance Id or Snapshot Id') + if not snap: + raise UserWarning("no snapshot selected") -def full_prompt(sess: boto3.Session) -> str: - """Prompts the user for all information. + return snap - This is run when dsnap get is run without any options. First we prompt for the EC2 - instance to run against, prompt again if there's if the instance has multiple - volumes, prompt again for snapshot if volume has multiple snapshots. - """ + +def vol_from_id(sess, i: str) -> 'r.Volume': + """download_from_id is meant to be called from the cli commands and will exit in the case of an error""" ec2: 'r.EC2ServiceResource' = sess.resource('ec2') - inst = instance_prompt(ec2.instances.filter( - Filters=[{'Name': 'instance-state-name', 'Values': ['running']}] - )) - vol = volume_prompt(inst.volumes) - snap = snapshot_prompt(vol.snapshots) or ask_to_create_snapshot(vol) - return snap.snapshot_id + if not i: + inst: 'r.Instance' = resource_prompt(ec2.instances.all(), '[PrivateDnsName, VpcId, SubnetId]') + vol: 'r.Volume' = resource_prompt(inst.volumes.all(), 'Attachments[*].Device') + elif i.startswith('vol-'): + vol = ec2.Volume(i) + elif i.startswith('i-'): + vol = resource_prompt(ec2.Instance(i).volumes.all(), 'Attachments[*].Device') + else: + raise UserWarning("unknown argument type, first argument should be an Instance Id or Snapshot Id") + + # vol will be None in cases of invalid argument or no snapshot was selected + if not vol: + fatal('Exiting...') + + return vol -def item_prompt(collection: ResourceCollection, jmespath_msg: str = None) -> ServiceResource: +def download_snap_id(sess, force, output, snap_id): + """download_from_id is meant to be called from the cli commands and will exit in the case of an error""" + print(f"Selected snapshot with id {snap_id}") + path = (output and output.absolute().as_posix()) or f"{snap_id}.img" + LocalSnapshot(path, snap_id, boto3_session=sess).fetch(force=force) + + +T = TypeVar('T') + + +def item_prompt(resources: Iterable[T], jmespath_msg: str = None) -> T: """Prompt's the user for an item to select from the items passed. Item is expected to support the Item protocol.""" - items = list(collection.all()) - if len(items) == 0: - raise UserWarning(f'No items found when calling {collection._py_operation_name}') + resources = cast(ResourceCollection, resources) + + items = list(resources.all()) + if not resources or len(items) == 0: + raise UserWarning(f'no items found when calling {resources._py_operation_name}') + elif len(items) == 1: + # No need to make a selection if there's only one option + return list(items)[0] msg = '' for i, item in enumerate(items): @@ -64,31 +112,12 @@ def item_prompt(collection: ResourceCollection, jmespath_msg: str = None) -> Ser return list(items)[answer] except IndexError: print(f"Invalid selection, valid inputs are 0 through {len(items) - 1}", file=sys.stderr) - return item_prompt(collection) - - -# Called if no snapshot_id is specified when running get -def instance_prompt(instances: ResourceCollection) -> 'r.Instance': - """Prompts the user to select an EC2 Instance of passed in instances""" - return cast('r.Instance', item_prompt(instances, jmespath_msg='[PrivateDnsName, VpcId, SubnetId]')) + return item_prompt(resources) -def snapshot_prompt(snapshots: ResourceCollection) -> Optional['r.Snapshot']: - """Prompts the user to select a EC2 Snapshot of passed in snapshots""" - snaps = list(snapshots.all()) - if len(snaps) == 0: - return None - elif len(snaps) == 1: - snap = snaps[0] - else: - snap = cast('r.Snapshot', item_prompt(snapshots, jmespath_msg='[StartTime, OwnerId, Description]')) - return snap - - -def volume_prompt(volumes: ResourceCollection) -> 'r.Volume': - """Prompts the user to select a Volume of passed in volumes""" - vols: List['r.Volume'] = list(volumes.all()) - return vols[0] if len(vols) == 1 else cast('r.Volume', item_prompt(volumes, jmespath_msg='Attachments[*].Device')) +def resource_prompt(resource: 'Iterable[T]', jmespath_msg='') -> T: + resource = cast('ResourceCollection', resource) + return item_prompt(resource, jmespath_msg=jmespath_msg) def ask_to_run(msg, func): @@ -105,3 +134,21 @@ def ask_to_create_snapshot(vol: 'r.Volume') -> 'r.Snapshot': delete the snapshot on exit. """ return ask_to_run("No snapshots found, create one?", lambda: create_tmp_snap(vol)) + + +def take_snapshot(vol: 'r.Volume') -> 'r.Snapshot': + # volumes can be attached to more then one instance at a time so include all attachments in the description + devices = ', '.join([a['Device'] for a in vol.attachments]) + instances = ', '.join([a['InstanceId'] for a in vol.attachments]) + desc = f"Instance(s): {instances}, Volume: {vol.id}, Device: {devices}" + print(f"Creating snapshot for {desc}") + + snap = vol.create_snapshot( + Description=f'dsnap ({desc})', + TagSpecifications=[{ + 'ResourceType': 'snapshot', + 'Tags': [{'Key': 'dsnap', 'Value': 'true'}] + }] + ) + snap.wait_until_completed() + return snap diff --git a/dsnap/snapshot.py b/dsnap/snapshot.py index 143bdcf..02ad56a 100644 --- a/dsnap/snapshot.py +++ b/dsnap/snapshot.py @@ -4,7 +4,7 @@ from pathlib import Path from queue import Queue, Empty from threading import Thread -from typing import TYPE_CHECKING, List, NamedTuple +from typing import TYPE_CHECKING, List, Callable import botocore.config from botocore.response import StreamingBody @@ -18,15 +18,49 @@ import boto3.resources MEGABYTE: int = 1024 * 1024 -GIBIBYTE: int = 1024 * MEGABYTE +GIGABYTE: int = 1024 * MEGABYTE -FETCH_THREADS = 30 +RUN_THREADS = 50 -class Block(NamedTuple): - BlockData: StreamingBody - Offset: int - Checksum: str +class Block: + client = boto3.client + + def __init__(self, snap: 'Snapshot', resp: 'BlockTypeDef'): + self.snapshot = snap + self.BlockIndex = resp['BlockIndex'] + self.Offset: int = resp['BlockIndex'] * snap.block_size_b + # When using the list_changed_blocks api the process is mostly the same except that we just care about the + # seecond block token. The first block token would have already been copied over locally and is what we'll be + # overwriting. + self.BlockToken = resp['BlockToken'] + self.BlockData: StreamingBody = None # type: ignore[assignment] + self.Checksum: str = '' + + def write(self) -> int: + """Takes a WriteBlock object to write to disk and yields the number of MiB's for each write.""" + logging.debug(f"Writing block at offset {self.Offset}") + data = self.BlockData.read() + + if not sha256_check(data, self.Checksum): + raise UserWarning(f"Got block with incorrect checksum at block offset {self.Offset}") + + with os.fdopen(os.open(self.snapshot.path, os.O_RDWR | os.O_CREAT), 'rb+') as f: + f.seek(self.Offset) + bytes_written = f.write(data) + f.flush() + return bytes_written + + def fetch(self) -> 'Block': + logging.debug(f"Getting block index {self.BlockIndex}") + resp = self.snapshot.ebs.get_snapshot_block( + SnapshotId=self.snapshot.snapshot_id, + BlockIndex=self.BlockIndex, + BlockToken=self.BlockToken, + ) + self.BlockData = resp['BlockData'] + self.Checksum = resp['Checksum'] + return self class Snapshot: @@ -36,13 +70,14 @@ def __init__( boto3_session: boto3.session.Session = boto3.session.Session(region_name='us-east-1'), botocore_conf: botocore.config.Config = botocore.config.Config() ) -> None: + self.blocks: List[Block] = [] self.snapshot_id = snapshot_id - self.output_file = '' + self.path = '' self.queue: Queue = Queue() # Make sure the number of connections matches the number of threads we run when fetching the EBS snapshot - ebs_config = botocore.config.Config(max_pool_connections=FETCH_THREADS).merge(botocore_conf) + ebs_config = botocore.config.Config(max_pool_connections=RUN_THREADS).merge(botocore_conf) self.ebs: EBSClient = boto3_session.client('ebs', config=ebs_config) self.volume_size_b = 0 @@ -50,19 +85,25 @@ def __init__( self.blocks_written = 0 self.block_size_b = 0 - def get_blocks(self) -> List['BlockTypeDef']: + def get_blocks(self) -> List[Block]: """Retrieves the list of blocks for self.snapshot_id. - This is called by self.download and doesn't need to be called explicitly before hand. + Various attributes are set when calling this method, best to call this early. """ + for block in self._get_blocks(): + self.blocks.append(Block(self, block)) + return self.blocks + + def _get_blocks(self) -> List['BlockTypeDef']: resp = self.ebs.list_snapshot_blocks(SnapshotId=self.snapshot_id) + # BlockIndex is equal to 512 KiB and seek uses bytes. + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ebs.html#EBS.Client.put_snapshot_block self.block_size_b = resp['BlockSize'] - self.volume_size_b = resp['VolumeSize'] * GIBIBYTE + self.volume_size_b = resp['VolumeSize'] * GIGABYTE logging.info(f"Volume size is {self.volume_size_b}") blocks = resp['Blocks'] - while resp.get('NextToken'): resp = self.ebs.list_snapshot_blocks(SnapshotId=self.snapshot_id, NextToken=resp.get('NextToken')) blocks.extend(resp['Blocks']) @@ -72,87 +113,74 @@ def get_blocks(self) -> List['BlockTypeDef']: return blocks - def download(self, output_file: str, force: bool = False) -> None: - """Downloads self.snapshot_id to the given output_file. + def run(self, func: Callable[[Block], None], threads=RUN_THREADS): + """Calls func on each block passing it a Block object. - If force is true output_file will be overwritten. + Run's across number of threads passed in `threads`, this defaults to 50. """ - - assert output_file - self.get_blocks() - - if Path(output_file).exists() and not force: - raise UserWarning(f"The output file '{output_file}' already exists.") - - self.output_file = os.path.abspath(output_file) - self.truncate() + for block in self.blocks: + logging.debug(f"Putting block index {block.BlockIndex} on the queue") + self.queue.put(block) threads = list() - for i in range(FETCH_THREADS): - t = Thread(target=self._write_blocks_worker) + for i in range(RUN_THREADS): + t = Thread(target=lambda: self._run(func)) threads.append(t) t.start() - blocks = self.get_blocks() - for block in blocks: - logging.debug(f"Putting block index {block['BlockIndex']} on the queue") - self.queue.put(block) - self.queue.join() - for t in threads: t.join() - print(f"Output Path: {output_file}") - - def truncate(self) -> None: - """Truncates self.output_file to size self.volume_size_b.""" - with open(self.output_file, 'wb') as f: - print(f"Truncating file to {self.volume_size_b}", file=sys.stderr) - f.truncate(self.volume_size_b) - f.flush() - - def _write_blocks_worker(self) -> None: - while self.total_blocks != self.blocks_written: + def _run(self, f: Callable[[Block], None]) -> None: + while True: try: - block: 'BlockTypeDef' = self.queue.get(timeout=0.2) - self._write_block(self._fetch_block(block)) + block: Block = self.queue.get(block=False) + f(block) self.blocks_written += 1 print(f"Saved block {self.blocks_written} of {self.total_blocks}", end='\r', file=sys.stderr) self.queue.task_done() except Exception as e: if isinstance(e, Empty): - continue + return else: logging.exception(f"[ERROR] {e.args}") - raise e - - def _fetch_block(self, block: 'BlockTypeDef') -> Block: - logging.debug(f"Getting block index {block['BlockIndex']}") - resp = self.ebs.get_snapshot_block( - SnapshotId=self.snapshot_id, - BlockIndex=block['BlockIndex'], - BlockToken=block['BlockToken'], - ) + raise e - # BlockIndex is equal to 512 KiB and seek uses bytes. - # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ebs.html#EBS.Client.put_snapshot_block - return Block( - Offset=block['BlockIndex'] * self.block_size_b, - BlockData=resp['BlockData'], - Checksum=resp['Checksum'] - ) - def _write_block(self, block: Block) -> int: - """Takes a WriteBlock object to write to disk and yields the number of MiB's for each write.""" - logging.debug(f"Writing block at offset {block.Offset}") - data = block.BlockData.read() +class LocalSnapshot(Snapshot): + def __init__( + self, + dir: str, + snapshot_id: str, + boto3_session: boto3.session.Session = boto3.session.Session(region_name='us-east-1'), + botocore_conf: botocore.config.Config = botocore.config.Config() + ) -> None: + super().__init__(snapshot_id, boto3_session, botocore_conf) - if not sha256_check(data, block.Checksum): - raise UserWarning(f"Got block with incorrect checksum at block offset {block.Offset}") + assert dir + self.path = str(Path(dir).joinpath(f"{snapshot_id}.img")) - with os.fdopen(os.open(self.output_file, os.O_RDWR | os.O_CREAT), 'rb+') as f: - f.seek(block.Offset) - bytes_written = f.write(data) + def fetch(self, force: bool = False) -> None: + """Downloads self.snapshot_id to the self.path. + + If force is true output_file will be overwritten. + """ + if Path(self.path).exists() and not force: + raise FileExistsError(f"The output file '{self.path}' already exists.") + self.path = os.path.abspath(self.path) + print(f"Output Path: {self.path}") + + self.get_blocks() + self.truncate() + + def download(b: Block): + b.fetch().write() + self.run(download) + + def truncate(self) -> None: + """Truncates self.output_file to size self.volume_size_b.""" + with open(self.path, 'wb') as f: + print(f"Truncating file to {self.volume_size_b/GIGABYTE} GB", file=sys.stderr) + f.truncate(self.volume_size_b) f.flush() - return bytes_written diff --git a/dsnap/utils.py b/dsnap/utils.py index c97b41b..d08b4db 100644 --- a/dsnap/utils.py +++ b/dsnap/utils.py @@ -71,10 +71,15 @@ def sha256_check(data: bytes, digest: str) -> bool: def init_vagrant(out_dir: Path = Path('.'), force=False) -> Optional[Path]: """Initializes out_dir directory with a templated Vagrantfile for mounting downloaded images""" - template = Path(__file__).parent.joinpath(Path('templates/Vagrantfile')) + template = Path(__file__).parent.joinpath(Path('files/Vagrantfile')) out = out_dir.joinpath(Path('Vagrantfile').name) if out.exists() and not force: return None else: out.write_text(template.read_text()) return out + + +def fatal(*msg: str): + logging.fatal('\n'.join(msg)) + exit(1) diff --git a/events/snapshot-created.json b/events/snapshot-created.json new file mode 100644 index 0000000..ee5e09d --- /dev/null +++ b/events/snapshot-created.json @@ -0,0 +1,22 @@ +{ + "version": "0", + "id": "01234567-0123-0123-0123-012345678901", + "detail-type": "EBS Snapshot Notification", + "source": "aws.ec2", + "account": "123456789012", + "time": "2016-11-01T13:12:22Z", + "region": "us-east-1", + "resources": [ + "arn:aws:ec2::us-east-2:snapshot/snap-02c3974406b3f3187" + ], + "detail": { + "event": "createSnapshot", + "result": "succeeded", + "cause": "", + "request-id": "", + "snapshot_id": "arn:aws:ec2::us-east-2:snapshot/snap-02c3974406b3f3187", + "source": "arn:aws:ec2::us-east-2:volume/vol-03b76fcd64d76335a", + "StartTime": "2020-11-01T13:00:12Z", + "EndTime": "2020-11-01T13:12:22Z" + } +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index b1a529a..4fa0b9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,14 @@ +[[package]] +name = "arrow" +version = "0.17.0" +description = "Better dates & times for Python" +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +python-dateutil = ">=2.7.0" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -20,6 +31,49 @@ docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +[[package]] +name = "aws-lambda-builders" +version = "1.2.0" +description = "Python library to compile, build & package AWS Lambda functions for several runtimes & frameworks." +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +six = ">=1.11,<2.0" + +[package.extras] +dev = ["coverage (==5.3)", "pytest-cov (==2.10.1)", "parameterized (==0.7.4)", "futures (==3.2.0)", "pathlib2 (==2.3.2)", "astroid (>=1.6.0,<1.7.0)", "pylint (>=1.9.5,<1.10.0)", "pytest (>=4.6.11,<4.7.0)", "mock (==3.0.5)", "backports.tempfile (==1.0)", "flake8 (==3.3.0)", "isort (>=4.2.5,<5)", "pylint (>=2.6.0,<2.7.0)", "pytest (>=6.1.1)", "mock (==4.0.2)", "black (==20.8b1)", "flake8 (==3.8.4)"] + +[[package]] +name = "aws-sam-cli" +version = "1.18.1" +description = "AWS SAM CLI is a CLI tool for local development and testing of Serverless applications" +category = "dev" +optional = true +python-versions = ">=3.6, <=4.0, !=4.0" + +[package.dependencies] +aws-lambda-builders = "1.2.0" +aws-sam-translator = "1.34.0" +boto3 = ">=1.14,<2.0" +chevron = ">=0.12,<1.0" +click = ">=7.1,<8.0" +cookiecutter = ">=1.7.2,<1.8.0" +dateparser = ">=0.7,<1.0" +docker = ">=4.2.0,<4.3.0" +Flask = ">=1.1.2,<1.2.0" +jmespath = ">=0.10.0,<0.11.0" +python-dateutil = ">=2.6,<2.8.1" +PyYAML = ">=5.3,<6.0" +requests = "2.23.0" +serverlessrepo = "0.1.10" +tomlkit = "0.7.0" +watchdog = "0.10.3" + +[package.extras] +dev = ["coverage (==5.3)", "pytest-cov (==2.10.1)", "pylint (>=2.6.0,<2.7.0)", "mypy (==0.790)", "boto3-stubs[essential] (>=1.14,<2.0)", "pytest (==6.1.1)", "parameterized (==0.7.4)", "pytest-xdist (==2.1.0)", "pytest-forked (==1.3.0)", "pytest-timeout (==1.4.2)", "pytest-rerunfailures (==9.1.1)", "black (==20.8b1)"] + [[package]] name = "aws-sam-translator" version = "1.34.0" @@ -50,6 +104,17 @@ future = "*" jsonpickle = "*" wrapt = "*" +[[package]] +name = "binaryornot" +version = "0.4.4" +description = "Ultra-lightweight pure Python package to check if a file is binary or text." +category = "dev" +optional = true +python-versions = "*" + +[package.dependencies] +chardet = ">=3.0.2" + [[package]] name = "boto" version = "2.49.0" @@ -60,295 +125,295 @@ python-versions = "*" [[package]] name = "boto3" -version = "1.17.1" +version = "1.17.7" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] -botocore = ">=1.20.1,<1.21.0" +botocore = ">=1.20.7,<1.21.0" jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" [[package]] name = "boto3-stubs" -version = "1.17.3.0" -description = "Type annotations for boto3 1.17.3, generated by mypy-boto3-buider 4.4.0" +version = "1.17.7.0" +description = "Type annotations for boto3 1.17.7, generated by mypy-boto3-buider 4.4.0" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -mypy-boto3-ebs = {version = "1.17.3.0", optional = true, markers = "extra == \"ebs\""} -mypy-boto3-ec2 = {version = "1.17.3.0", optional = true, markers = "extra == \"ec2\""} +mypy-boto3-ebs = {version = "1.17.7.0", optional = true, markers = "extra == \"ebs\""} +mypy-boto3-ec2 = {version = "1.17.7.0", optional = true, markers = "extra == \"ec2\""} typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -accessanalyzer = ["mypy-boto3-accessanalyzer (==1.17.3.0)"] -acm = ["mypy-boto3-acm (==1.17.3.0)"] -acm-pca = ["mypy-boto3-acm-pca (==1.17.3.0)"] -alexaforbusiness = ["mypy-boto3-alexaforbusiness (==1.17.3.0)"] -all = ["mypy-boto3-accessanalyzer (==1.17.3.0)", "mypy-boto3-acm (==1.17.3.0)", "mypy-boto3-acm-pca (==1.17.3.0)", "mypy-boto3-alexaforbusiness (==1.17.3.0)", "mypy-boto3-amp (==1.17.3.0)", "mypy-boto3-amplify (==1.17.3.0)", "mypy-boto3-amplifybackend (==1.17.3.0)", "mypy-boto3-apigateway (==1.17.3.0)", "mypy-boto3-apigatewaymanagementapi (==1.17.3.0)", "mypy-boto3-apigatewayv2 (==1.17.3.0)", "mypy-boto3-appconfig (==1.17.3.0)", "mypy-boto3-appflow (==1.17.3.0)", "mypy-boto3-appintegrations (==1.17.3.0)", "mypy-boto3-application-autoscaling (==1.17.3.0)", "mypy-boto3-application-insights (==1.17.3.0)", "mypy-boto3-appmesh (==1.17.3.0)", "mypy-boto3-appstream (==1.17.3.0)", "mypy-boto3-appsync (==1.17.3.0)", "mypy-boto3-athena (==1.17.3.0)", "mypy-boto3-auditmanager (==1.17.3.0)", "mypy-boto3-autoscaling (==1.17.3.0)", "mypy-boto3-autoscaling-plans (==1.17.3.0)", "mypy-boto3-backup (==1.17.3.0)", "mypy-boto3-batch (==1.17.3.0)", "mypy-boto3-braket (==1.17.3.0)", "mypy-boto3-budgets (==1.17.3.0)", "mypy-boto3-ce (==1.17.3.0)", "mypy-boto3-chime (==1.17.3.0)", "mypy-boto3-cloud9 (==1.17.3.0)", "mypy-boto3-clouddirectory (==1.17.3.0)", "mypy-boto3-cloudformation (==1.17.3.0)", "mypy-boto3-cloudfront (==1.17.3.0)", "mypy-boto3-cloudhsm (==1.17.3.0)", "mypy-boto3-cloudhsmv2 (==1.17.3.0)", "mypy-boto3-cloudsearch (==1.17.3.0)", "mypy-boto3-cloudsearchdomain (==1.17.3.0)", "mypy-boto3-cloudtrail (==1.17.3.0)", "mypy-boto3-cloudwatch (==1.17.3.0)", "mypy-boto3-codeartifact (==1.17.3.0)", "mypy-boto3-codebuild (==1.17.3.0)", "mypy-boto3-codecommit (==1.17.3.0)", "mypy-boto3-codedeploy (==1.17.3.0)", "mypy-boto3-codeguru-reviewer (==1.17.3.0)", "mypy-boto3-codeguruprofiler (==1.17.3.0)", "mypy-boto3-codepipeline (==1.17.3.0)", "mypy-boto3-codestar (==1.17.3.0)", "mypy-boto3-codestar-connections (==1.17.3.0)", "mypy-boto3-codestar-notifications (==1.17.3.0)", "mypy-boto3-cognito-identity (==1.17.3.0)", "mypy-boto3-cognito-idp (==1.17.3.0)", "mypy-boto3-cognito-sync (==1.17.3.0)", "mypy-boto3-comprehend (==1.17.3.0)", "mypy-boto3-comprehendmedical (==1.17.3.0)", "mypy-boto3-compute-optimizer (==1.17.3.0)", "mypy-boto3-config (==1.17.3.0)", "mypy-boto3-connect (==1.17.3.0)", "mypy-boto3-connect-contact-lens (==1.17.3.0)", "mypy-boto3-connectparticipant (==1.17.3.0)", "mypy-boto3-cur (==1.17.3.0)", "mypy-boto3-customer-profiles (==1.17.3.0)", "mypy-boto3-databrew (==1.17.3.0)", "mypy-boto3-dataexchange (==1.17.3.0)", "mypy-boto3-datapipeline (==1.17.3.0)", "mypy-boto3-datasync (==1.17.3.0)", "mypy-boto3-dax (==1.17.3.0)", "mypy-boto3-detective (==1.17.3.0)", "mypy-boto3-devicefarm (==1.17.3.0)", "mypy-boto3-devops-guru (==1.17.3.0)", "mypy-boto3-directconnect (==1.17.3.0)", "mypy-boto3-discovery (==1.17.3.0)", "mypy-boto3-dlm (==1.17.3.0)", "mypy-boto3-dms (==1.17.3.0)", "mypy-boto3-docdb (==1.17.3.0)", "mypy-boto3-ds (==1.17.3.0)", "mypy-boto3-dynamodb (==1.17.3.0)", "mypy-boto3-dynamodbstreams (==1.17.3.0)", "mypy-boto3-ebs (==1.17.3.0)", "mypy-boto3-ec2 (==1.17.3.0)", "mypy-boto3-ec2-instance-connect (==1.17.3.0)", "mypy-boto3-ecr (==1.17.3.0)", "mypy-boto3-ecr-public (==1.17.3.0)", "mypy-boto3-ecs (==1.17.3.0)", "mypy-boto3-efs (==1.17.3.0)", "mypy-boto3-eks (==1.17.3.0)", "mypy-boto3-elastic-inference (==1.17.3.0)", "mypy-boto3-elasticache (==1.17.3.0)", "mypy-boto3-elasticbeanstalk (==1.17.3.0)", "mypy-boto3-elastictranscoder (==1.17.3.0)", "mypy-boto3-elb (==1.17.3.0)", "mypy-boto3-elbv2 (==1.17.3.0)", "mypy-boto3-emr (==1.17.3.0)", "mypy-boto3-emr-containers (==1.17.3.0)", "mypy-boto3-es (==1.17.3.0)", "mypy-boto3-events (==1.17.3.0)", "mypy-boto3-firehose (==1.17.3.0)", "mypy-boto3-fms (==1.17.3.0)", "mypy-boto3-forecast (==1.17.3.0)", "mypy-boto3-forecastquery (==1.17.3.0)", "mypy-boto3-frauddetector (==1.17.3.0)", "mypy-boto3-fsx (==1.17.3.0)", "mypy-boto3-gamelift (==1.17.3.0)", "mypy-boto3-glacier (==1.17.3.0)", "mypy-boto3-globalaccelerator (==1.17.3.0)", "mypy-boto3-glue (==1.17.3.0)", "mypy-boto3-greengrass (==1.17.3.0)", "mypy-boto3-greengrassv2 (==1.17.3.0)", "mypy-boto3-groundstation (==1.17.3.0)", "mypy-boto3-guardduty (==1.17.3.0)", "mypy-boto3-health (==1.17.3.0)", "mypy-boto3-healthlake (==1.17.3.0)", "mypy-boto3-honeycode (==1.17.3.0)", "mypy-boto3-iam (==1.17.3.0)", "mypy-boto3-identitystore (==1.17.3.0)", "mypy-boto3-imagebuilder (==1.17.3.0)", "mypy-boto3-importexport (==1.17.3.0)", "mypy-boto3-inspector (==1.17.3.0)", "mypy-boto3-iot (==1.17.3.0)", "mypy-boto3-iot-data (==1.17.3.0)", "mypy-boto3-iot-jobs-data (==1.17.3.0)", "mypy-boto3-iot1click-devices (==1.17.3.0)", "mypy-boto3-iot1click-projects (==1.17.3.0)", "mypy-boto3-iotanalytics (==1.17.3.0)", "mypy-boto3-iotdeviceadvisor (==1.17.3.0)", "mypy-boto3-iotevents (==1.17.3.0)", "mypy-boto3-iotevents-data (==1.17.3.0)", "mypy-boto3-iotfleethub (==1.17.3.0)", "mypy-boto3-iotsecuretunneling (==1.17.3.0)", "mypy-boto3-iotsitewise (==1.17.3.0)", "mypy-boto3-iotthingsgraph (==1.17.3.0)", "mypy-boto3-iotwireless (==1.17.3.0)", "mypy-boto3-ivs (==1.17.3.0)", "mypy-boto3-kafka (==1.17.3.0)", "mypy-boto3-kendra (==1.17.3.0)", "mypy-boto3-kinesis (==1.17.3.0)", "mypy-boto3-kinesis-video-archived-media (==1.17.3.0)", "mypy-boto3-kinesis-video-media (==1.17.3.0)", "mypy-boto3-kinesis-video-signaling (==1.17.3.0)", "mypy-boto3-kinesisanalytics (==1.17.3.0)", "mypy-boto3-kinesisanalyticsv2 (==1.17.3.0)", "mypy-boto3-kinesisvideo (==1.17.3.0)", "mypy-boto3-kms (==1.17.3.0)", "mypy-boto3-lakeformation (==1.17.3.0)", "mypy-boto3-lambda (==1.17.3.0)", "mypy-boto3-lex-models (==1.17.3.0)", "mypy-boto3-lex-runtime (==1.17.3.0)", "mypy-boto3-lexv2-models (==1.17.3.0)", "mypy-boto3-lexv2-runtime (==1.17.3.0)", "mypy-boto3-license-manager (==1.17.3.0)", "mypy-boto3-lightsail (==1.17.3.0)", "mypy-boto3-location (==1.17.3.0)", "mypy-boto3-logs (==1.17.3.0)", "mypy-boto3-lookoutvision (==1.17.3.0)", "mypy-boto3-machinelearning (==1.17.3.0)", "mypy-boto3-macie (==1.17.3.0)", "mypy-boto3-macie2 (==1.17.3.0)", "mypy-boto3-managedblockchain (==1.17.3.0)", "mypy-boto3-marketplace-catalog (==1.17.3.0)", "mypy-boto3-marketplace-entitlement (==1.17.3.0)", "mypy-boto3-marketplacecommerceanalytics (==1.17.3.0)", "mypy-boto3-mediaconnect (==1.17.3.0)", "mypy-boto3-mediaconvert (==1.17.3.0)", "mypy-boto3-medialive (==1.17.3.0)", "mypy-boto3-mediapackage (==1.17.3.0)", "mypy-boto3-mediapackage-vod (==1.17.3.0)", "mypy-boto3-mediastore (==1.17.3.0)", "mypy-boto3-mediastore-data (==1.17.3.0)", "mypy-boto3-mediatailor (==1.17.3.0)", "mypy-boto3-meteringmarketplace (==1.17.3.0)", "mypy-boto3-mgh (==1.17.3.0)", "mypy-boto3-migrationhub-config (==1.17.3.0)", "mypy-boto3-mobile (==1.17.3.0)", "mypy-boto3-mq (==1.17.3.0)", "mypy-boto3-mturk (==1.17.3.0)", "mypy-boto3-mwaa (==1.17.3.0)", "mypy-boto3-neptune (==1.17.3.0)", "mypy-boto3-network-firewall (==1.17.3.0)", "mypy-boto3-networkmanager (==1.17.3.0)", "mypy-boto3-opsworks (==1.17.3.0)", "mypy-boto3-opsworkscm (==1.17.3.0)", "mypy-boto3-organizations (==1.17.3.0)", "mypy-boto3-outposts (==1.17.3.0)", "mypy-boto3-personalize (==1.17.3.0)", "mypy-boto3-personalize-events (==1.17.3.0)", "mypy-boto3-personalize-runtime (==1.17.3.0)", "mypy-boto3-pi (==1.17.3.0)", "mypy-boto3-pinpoint (==1.17.3.0)", "mypy-boto3-pinpoint-email (==1.17.3.0)", "mypy-boto3-pinpoint-sms-voice (==1.17.3.0)", "mypy-boto3-polly (==1.17.3.0)", "mypy-boto3-pricing (==1.17.3.0)", "mypy-boto3-qldb (==1.17.3.0)", "mypy-boto3-qldb-session (==1.17.3.0)", "mypy-boto3-quicksight (==1.17.3.0)", "mypy-boto3-ram (==1.17.3.0)", "mypy-boto3-rds (==1.17.3.0)", "mypy-boto3-rds-data (==1.17.3.0)", "mypy-boto3-redshift (==1.17.3.0)", "mypy-boto3-redshift-data (==1.17.3.0)", "mypy-boto3-rekognition (==1.17.3.0)", "mypy-boto3-resource-groups (==1.17.3.0)", "mypy-boto3-resourcegroupstaggingapi (==1.17.3.0)", "mypy-boto3-robomaker (==1.17.3.0)", "mypy-boto3-route53 (==1.17.3.0)", "mypy-boto3-route53domains (==1.17.3.0)", "mypy-boto3-route53resolver (==1.17.3.0)", "mypy-boto3-s3 (==1.17.3.0)", "mypy-boto3-s3control (==1.17.3.0)", "mypy-boto3-s3outposts (==1.17.3.0)", "mypy-boto3-sagemaker (==1.17.3.0)", "mypy-boto3-sagemaker-a2i-runtime (==1.17.3.0)", "mypy-boto3-sagemaker-edge (==1.17.3.0)", "mypy-boto3-sagemaker-featurestore-runtime (==1.17.3.0)", "mypy-boto3-sagemaker-runtime (==1.17.3.0)", "mypy-boto3-savingsplans (==1.17.3.0)", "mypy-boto3-schemas (==1.17.3.0)", "mypy-boto3-sdb (==1.17.3.0)", "mypy-boto3-secretsmanager (==1.17.3.0)", "mypy-boto3-securityhub (==1.17.3.0)", "mypy-boto3-serverlessrepo (==1.17.3.0)", "mypy-boto3-service-quotas (==1.17.3.0)", "mypy-boto3-servicecatalog (==1.17.3.0)", "mypy-boto3-servicecatalog-appregistry (==1.17.3.0)", "mypy-boto3-servicediscovery (==1.17.3.0)", "mypy-boto3-ses (==1.17.3.0)", "mypy-boto3-sesv2 (==1.17.3.0)", "mypy-boto3-shield (==1.17.3.0)", "mypy-boto3-signer (==1.17.3.0)", "mypy-boto3-sms (==1.17.3.0)", "mypy-boto3-sms-voice (==1.17.3.0)", "mypy-boto3-snowball (==1.17.3.0)", "mypy-boto3-sns (==1.17.3.0)", "mypy-boto3-sqs (==1.17.3.0)", "mypy-boto3-ssm (==1.17.3.0)", "mypy-boto3-sso (==1.17.3.0)", "mypy-boto3-sso-admin (==1.17.3.0)", "mypy-boto3-sso-oidc (==1.17.3.0)", "mypy-boto3-stepfunctions (==1.17.3.0)", "mypy-boto3-storagegateway (==1.17.3.0)", "mypy-boto3-sts (==1.17.3.0)", "mypy-boto3-support (==1.17.3.0)", "mypy-boto3-swf (==1.17.3.0)", "mypy-boto3-synthetics (==1.17.3.0)", "mypy-boto3-textract (==1.17.3.0)", "mypy-boto3-timestream-query (==1.17.3.0)", "mypy-boto3-timestream-write (==1.17.3.0)", "mypy-boto3-transcribe (==1.17.3.0)", "mypy-boto3-transfer (==1.17.3.0)", "mypy-boto3-translate (==1.17.3.0)", "mypy-boto3-waf (==1.17.3.0)", "mypy-boto3-waf-regional (==1.17.3.0)", "mypy-boto3-wafv2 (==1.17.3.0)", "mypy-boto3-wellarchitected (==1.17.3.0)", "mypy-boto3-workdocs (==1.17.3.0)", "mypy-boto3-worklink (==1.17.3.0)", "mypy-boto3-workmail (==1.17.3.0)", "mypy-boto3-workmailmessageflow (==1.17.3.0)", "mypy-boto3-workspaces (==1.17.3.0)", "mypy-boto3-xray (==1.17.3.0)"] -amp = ["mypy-boto3-amp (==1.17.3.0)"] -amplify = ["mypy-boto3-amplify (==1.17.3.0)"] -amplifybackend = ["mypy-boto3-amplifybackend (==1.17.3.0)"] -apigateway = ["mypy-boto3-apigateway (==1.17.3.0)"] -apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (==1.17.3.0)"] -apigatewayv2 = ["mypy-boto3-apigatewayv2 (==1.17.3.0)"] -appconfig = ["mypy-boto3-appconfig (==1.17.3.0)"] -appflow = ["mypy-boto3-appflow (==1.17.3.0)"] -appintegrations = ["mypy-boto3-appintegrations (==1.17.3.0)"] -application-autoscaling = ["mypy-boto3-application-autoscaling (==1.17.3.0)"] -application-insights = ["mypy-boto3-application-insights (==1.17.3.0)"] -appmesh = ["mypy-boto3-appmesh (==1.17.3.0)"] -appstream = ["mypy-boto3-appstream (==1.17.3.0)"] -appsync = ["mypy-boto3-appsync (==1.17.3.0)"] -athena = ["mypy-boto3-athena (==1.17.3.0)"] -auditmanager = ["mypy-boto3-auditmanager (==1.17.3.0)"] -autoscaling = ["mypy-boto3-autoscaling (==1.17.3.0)"] -autoscaling-plans = ["mypy-boto3-autoscaling-plans (==1.17.3.0)"] -backup = ["mypy-boto3-backup (==1.17.3.0)"] -batch = ["mypy-boto3-batch (==1.17.3.0)"] -braket = ["mypy-boto3-braket (==1.17.3.0)"] -budgets = ["mypy-boto3-budgets (==1.17.3.0)"] -ce = ["mypy-boto3-ce (==1.17.3.0)"] -chime = ["mypy-boto3-chime (==1.17.3.0)"] -cloud9 = ["mypy-boto3-cloud9 (==1.17.3.0)"] -clouddirectory = ["mypy-boto3-clouddirectory (==1.17.3.0)"] -cloudformation = ["mypy-boto3-cloudformation (==1.17.3.0)"] -cloudfront = ["mypy-boto3-cloudfront (==1.17.3.0)"] -cloudhsm = ["mypy-boto3-cloudhsm (==1.17.3.0)"] -cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (==1.17.3.0)"] -cloudsearch = ["mypy-boto3-cloudsearch (==1.17.3.0)"] -cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (==1.17.3.0)"] -cloudtrail = ["mypy-boto3-cloudtrail (==1.17.3.0)"] -cloudwatch = ["mypy-boto3-cloudwatch (==1.17.3.0)"] -codeartifact = ["mypy-boto3-codeartifact (==1.17.3.0)"] -codebuild = ["mypy-boto3-codebuild (==1.17.3.0)"] -codecommit = ["mypy-boto3-codecommit (==1.17.3.0)"] -codedeploy = ["mypy-boto3-codedeploy (==1.17.3.0)"] -codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (==1.17.3.0)"] -codeguruprofiler = ["mypy-boto3-codeguruprofiler (==1.17.3.0)"] -codepipeline = ["mypy-boto3-codepipeline (==1.17.3.0)"] -codestar = ["mypy-boto3-codestar (==1.17.3.0)"] -codestar-connections = ["mypy-boto3-codestar-connections (==1.17.3.0)"] -codestar-notifications = ["mypy-boto3-codestar-notifications (==1.17.3.0)"] -cognito-identity = ["mypy-boto3-cognito-identity (==1.17.3.0)"] -cognito-idp = ["mypy-boto3-cognito-idp (==1.17.3.0)"] -cognito-sync = ["mypy-boto3-cognito-sync (==1.17.3.0)"] -comprehend = ["mypy-boto3-comprehend (==1.17.3.0)"] -comprehendmedical = ["mypy-boto3-comprehendmedical (==1.17.3.0)"] -compute-optimizer = ["mypy-boto3-compute-optimizer (==1.17.3.0)"] -config = ["mypy-boto3-config (==1.17.3.0)"] -connect = ["mypy-boto3-connect (==1.17.3.0)"] -connect-contact-lens = ["mypy-boto3-connect-contact-lens (==1.17.3.0)"] -connectparticipant = ["mypy-boto3-connectparticipant (==1.17.3.0)"] -cur = ["mypy-boto3-cur (==1.17.3.0)"] -customer-profiles = ["mypy-boto3-customer-profiles (==1.17.3.0)"] -databrew = ["mypy-boto3-databrew (==1.17.3.0)"] -dataexchange = ["mypy-boto3-dataexchange (==1.17.3.0)"] -datapipeline = ["mypy-boto3-datapipeline (==1.17.3.0)"] -datasync = ["mypy-boto3-datasync (==1.17.3.0)"] -dax = ["mypy-boto3-dax (==1.17.3.0)"] -detective = ["mypy-boto3-detective (==1.17.3.0)"] -devicefarm = ["mypy-boto3-devicefarm (==1.17.3.0)"] -devops-guru = ["mypy-boto3-devops-guru (==1.17.3.0)"] -directconnect = ["mypy-boto3-directconnect (==1.17.3.0)"] -discovery = ["mypy-boto3-discovery (==1.17.3.0)"] -dlm = ["mypy-boto3-dlm (==1.17.3.0)"] -dms = ["mypy-boto3-dms (==1.17.3.0)"] -docdb = ["mypy-boto3-docdb (==1.17.3.0)"] -ds = ["mypy-boto3-ds (==1.17.3.0)"] -dynamodb = ["mypy-boto3-dynamodb (==1.17.3.0)"] -dynamodbstreams = ["mypy-boto3-dynamodbstreams (==1.17.3.0)"] -ebs = ["mypy-boto3-ebs (==1.17.3.0)"] -ec2 = ["mypy-boto3-ec2 (==1.17.3.0)"] -ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (==1.17.3.0)"] -ecr = ["mypy-boto3-ecr (==1.17.3.0)"] -ecr-public = ["mypy-boto3-ecr-public (==1.17.3.0)"] -ecs = ["mypy-boto3-ecs (==1.17.3.0)"] -efs = ["mypy-boto3-efs (==1.17.3.0)"] -eks = ["mypy-boto3-eks (==1.17.3.0)"] -elastic-inference = ["mypy-boto3-elastic-inference (==1.17.3.0)"] -elasticache = ["mypy-boto3-elasticache (==1.17.3.0)"] -elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (==1.17.3.0)"] -elastictranscoder = ["mypy-boto3-elastictranscoder (==1.17.3.0)"] -elb = ["mypy-boto3-elb (==1.17.3.0)"] -elbv2 = ["mypy-boto3-elbv2 (==1.17.3.0)"] -emr = ["mypy-boto3-emr (==1.17.3.0)"] -emr-containers = ["mypy-boto3-emr-containers (==1.17.3.0)"] -es = ["mypy-boto3-es (==1.17.3.0)"] -essential = ["mypy-boto3-cloudformation (==1.17.3.0)", "mypy-boto3-dynamodb (==1.17.3.0)", "mypy-boto3-ec2 (==1.17.3.0)", "mypy-boto3-lambda (==1.17.3.0)", "mypy-boto3-rds (==1.17.3.0)", "mypy-boto3-s3 (==1.17.3.0)", "mypy-boto3-sqs (==1.17.3.0)"] -events = ["mypy-boto3-events (==1.17.3.0)"] -firehose = ["mypy-boto3-firehose (==1.17.3.0)"] -fms = ["mypy-boto3-fms (==1.17.3.0)"] -forecast = ["mypy-boto3-forecast (==1.17.3.0)"] -forecastquery = ["mypy-boto3-forecastquery (==1.17.3.0)"] -frauddetector = ["mypy-boto3-frauddetector (==1.17.3.0)"] -fsx = ["mypy-boto3-fsx (==1.17.3.0)"] -gamelift = ["mypy-boto3-gamelift (==1.17.3.0)"] -glacier = ["mypy-boto3-glacier (==1.17.3.0)"] -globalaccelerator = ["mypy-boto3-globalaccelerator (==1.17.3.0)"] -glue = ["mypy-boto3-glue (==1.17.3.0)"] -greengrass = ["mypy-boto3-greengrass (==1.17.3.0)"] -greengrassv2 = ["mypy-boto3-greengrassv2 (==1.17.3.0)"] -groundstation = ["mypy-boto3-groundstation (==1.17.3.0)"] -guardduty = ["mypy-boto3-guardduty (==1.17.3.0)"] -health = ["mypy-boto3-health (==1.17.3.0)"] -healthlake = ["mypy-boto3-healthlake (==1.17.3.0)"] -honeycode = ["mypy-boto3-honeycode (==1.17.3.0)"] -iam = ["mypy-boto3-iam (==1.17.3.0)"] -identitystore = ["mypy-boto3-identitystore (==1.17.3.0)"] -imagebuilder = ["mypy-boto3-imagebuilder (==1.17.3.0)"] -importexport = ["mypy-boto3-importexport (==1.17.3.0)"] -inspector = ["mypy-boto3-inspector (==1.17.3.0)"] -iot = ["mypy-boto3-iot (==1.17.3.0)"] -iot-data = ["mypy-boto3-iot-data (==1.17.3.0)"] -iot-jobs-data = ["mypy-boto3-iot-jobs-data (==1.17.3.0)"] -iot1click-devices = ["mypy-boto3-iot1click-devices (==1.17.3.0)"] -iot1click-projects = ["mypy-boto3-iot1click-projects (==1.17.3.0)"] -iotanalytics = ["mypy-boto3-iotanalytics (==1.17.3.0)"] -iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (==1.17.3.0)"] -iotevents = ["mypy-boto3-iotevents (==1.17.3.0)"] -iotevents-data = ["mypy-boto3-iotevents-data (==1.17.3.0)"] -iotfleethub = ["mypy-boto3-iotfleethub (==1.17.3.0)"] -iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (==1.17.3.0)"] -iotsitewise = ["mypy-boto3-iotsitewise (==1.17.3.0)"] -iotthingsgraph = ["mypy-boto3-iotthingsgraph (==1.17.3.0)"] -iotwireless = ["mypy-boto3-iotwireless (==1.17.3.0)"] -ivs = ["mypy-boto3-ivs (==1.17.3.0)"] -kafka = ["mypy-boto3-kafka (==1.17.3.0)"] -kendra = ["mypy-boto3-kendra (==1.17.3.0)"] -kinesis = ["mypy-boto3-kinesis (==1.17.3.0)"] -kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (==1.17.3.0)"] -kinesis-video-media = ["mypy-boto3-kinesis-video-media (==1.17.3.0)"] -kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (==1.17.3.0)"] -kinesisanalytics = ["mypy-boto3-kinesisanalytics (==1.17.3.0)"] -kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (==1.17.3.0)"] -kinesisvideo = ["mypy-boto3-kinesisvideo (==1.17.3.0)"] -kms = ["mypy-boto3-kms (==1.17.3.0)"] -lakeformation = ["mypy-boto3-lakeformation (==1.17.3.0)"] -lambda = ["mypy-boto3-lambda (==1.17.3.0)"] -lex-models = ["mypy-boto3-lex-models (==1.17.3.0)"] -lex-runtime = ["mypy-boto3-lex-runtime (==1.17.3.0)"] -lexv2-models = ["mypy-boto3-lexv2-models (==1.17.3.0)"] -lexv2-runtime = ["mypy-boto3-lexv2-runtime (==1.17.3.0)"] -license-manager = ["mypy-boto3-license-manager (==1.17.3.0)"] -lightsail = ["mypy-boto3-lightsail (==1.17.3.0)"] -location = ["mypy-boto3-location (==1.17.3.0)"] -logs = ["mypy-boto3-logs (==1.17.3.0)"] -lookoutvision = ["mypy-boto3-lookoutvision (==1.17.3.0)"] -machinelearning = ["mypy-boto3-machinelearning (==1.17.3.0)"] -macie = ["mypy-boto3-macie (==1.17.3.0)"] -macie2 = ["mypy-boto3-macie2 (==1.17.3.0)"] -managedblockchain = ["mypy-boto3-managedblockchain (==1.17.3.0)"] -marketplace-catalog = ["mypy-boto3-marketplace-catalog (==1.17.3.0)"] -marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (==1.17.3.0)"] -marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (==1.17.3.0)"] -mediaconnect = ["mypy-boto3-mediaconnect (==1.17.3.0)"] -mediaconvert = ["mypy-boto3-mediaconvert (==1.17.3.0)"] -medialive = ["mypy-boto3-medialive (==1.17.3.0)"] -mediapackage = ["mypy-boto3-mediapackage (==1.17.3.0)"] -mediapackage-vod = ["mypy-boto3-mediapackage-vod (==1.17.3.0)"] -mediastore = ["mypy-boto3-mediastore (==1.17.3.0)"] -mediastore-data = ["mypy-boto3-mediastore-data (==1.17.3.0)"] -mediatailor = ["mypy-boto3-mediatailor (==1.17.3.0)"] -meteringmarketplace = ["mypy-boto3-meteringmarketplace (==1.17.3.0)"] -mgh = ["mypy-boto3-mgh (==1.17.3.0)"] -migrationhub-config = ["mypy-boto3-migrationhub-config (==1.17.3.0)"] -mobile = ["mypy-boto3-mobile (==1.17.3.0)"] -mq = ["mypy-boto3-mq (==1.17.3.0)"] -mturk = ["mypy-boto3-mturk (==1.17.3.0)"] -mwaa = ["mypy-boto3-mwaa (==1.17.3.0)"] -neptune = ["mypy-boto3-neptune (==1.17.3.0)"] -network-firewall = ["mypy-boto3-network-firewall (==1.17.3.0)"] -networkmanager = ["mypy-boto3-networkmanager (==1.17.3.0)"] -opsworks = ["mypy-boto3-opsworks (==1.17.3.0)"] -opsworkscm = ["mypy-boto3-opsworkscm (==1.17.3.0)"] -organizations = ["mypy-boto3-organizations (==1.17.3.0)"] -outposts = ["mypy-boto3-outposts (==1.17.3.0)"] -personalize = ["mypy-boto3-personalize (==1.17.3.0)"] -personalize-events = ["mypy-boto3-personalize-events (==1.17.3.0)"] -personalize-runtime = ["mypy-boto3-personalize-runtime (==1.17.3.0)"] -pi = ["mypy-boto3-pi (==1.17.3.0)"] -pinpoint = ["mypy-boto3-pinpoint (==1.17.3.0)"] -pinpoint-email = ["mypy-boto3-pinpoint-email (==1.17.3.0)"] -pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (==1.17.3.0)"] -polly = ["mypy-boto3-polly (==1.17.3.0)"] -pricing = ["mypy-boto3-pricing (==1.17.3.0)"] -qldb = ["mypy-boto3-qldb (==1.17.3.0)"] -qldb-session = ["mypy-boto3-qldb-session (==1.17.3.0)"] -quicksight = ["mypy-boto3-quicksight (==1.17.3.0)"] -ram = ["mypy-boto3-ram (==1.17.3.0)"] -rds = ["mypy-boto3-rds (==1.17.3.0)"] -rds-data = ["mypy-boto3-rds-data (==1.17.3.0)"] -redshift = ["mypy-boto3-redshift (==1.17.3.0)"] -redshift-data = ["mypy-boto3-redshift-data (==1.17.3.0)"] -rekognition = ["mypy-boto3-rekognition (==1.17.3.0)"] -resource-groups = ["mypy-boto3-resource-groups (==1.17.3.0)"] -resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (==1.17.3.0)"] -robomaker = ["mypy-boto3-robomaker (==1.17.3.0)"] -route53 = ["mypy-boto3-route53 (==1.17.3.0)"] -route53domains = ["mypy-boto3-route53domains (==1.17.3.0)"] -route53resolver = ["mypy-boto3-route53resolver (==1.17.3.0)"] -s3 = ["mypy-boto3-s3 (==1.17.3.0)"] -s3control = ["mypy-boto3-s3control (==1.17.3.0)"] -s3outposts = ["mypy-boto3-s3outposts (==1.17.3.0)"] -sagemaker = ["mypy-boto3-sagemaker (==1.17.3.0)"] -sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (==1.17.3.0)"] -sagemaker-edge = ["mypy-boto3-sagemaker-edge (==1.17.3.0)"] -sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (==1.17.3.0)"] -sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (==1.17.3.0)"] -savingsplans = ["mypy-boto3-savingsplans (==1.17.3.0)"] -schemas = ["mypy-boto3-schemas (==1.17.3.0)"] -sdb = ["mypy-boto3-sdb (==1.17.3.0)"] -secretsmanager = ["mypy-boto3-secretsmanager (==1.17.3.0)"] -securityhub = ["mypy-boto3-securityhub (==1.17.3.0)"] -serverlessrepo = ["mypy-boto3-serverlessrepo (==1.17.3.0)"] -service-quotas = ["mypy-boto3-service-quotas (==1.17.3.0)"] -servicecatalog = ["mypy-boto3-servicecatalog (==1.17.3.0)"] -servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (==1.17.3.0)"] -servicediscovery = ["mypy-boto3-servicediscovery (==1.17.3.0)"] -ses = ["mypy-boto3-ses (==1.17.3.0)"] -sesv2 = ["mypy-boto3-sesv2 (==1.17.3.0)"] -shield = ["mypy-boto3-shield (==1.17.3.0)"] -signer = ["mypy-boto3-signer (==1.17.3.0)"] -sms = ["mypy-boto3-sms (==1.17.3.0)"] -sms-voice = ["mypy-boto3-sms-voice (==1.17.3.0)"] -snowball = ["mypy-boto3-snowball (==1.17.3.0)"] -sns = ["mypy-boto3-sns (==1.17.3.0)"] -sqs = ["mypy-boto3-sqs (==1.17.3.0)"] -ssm = ["mypy-boto3-ssm (==1.17.3.0)"] -sso = ["mypy-boto3-sso (==1.17.3.0)"] -sso-admin = ["mypy-boto3-sso-admin (==1.17.3.0)"] -sso-oidc = ["mypy-boto3-sso-oidc (==1.17.3.0)"] -stepfunctions = ["mypy-boto3-stepfunctions (==1.17.3.0)"] -storagegateway = ["mypy-boto3-storagegateway (==1.17.3.0)"] -sts = ["mypy-boto3-sts (==1.17.3.0)"] -support = ["mypy-boto3-support (==1.17.3.0)"] -swf = ["mypy-boto3-swf (==1.17.3.0)"] -synthetics = ["mypy-boto3-synthetics (==1.17.3.0)"] -textract = ["mypy-boto3-textract (==1.17.3.0)"] -timestream-query = ["mypy-boto3-timestream-query (==1.17.3.0)"] -timestream-write = ["mypy-boto3-timestream-write (==1.17.3.0)"] -transcribe = ["mypy-boto3-transcribe (==1.17.3.0)"] -transfer = ["mypy-boto3-transfer (==1.17.3.0)"] -translate = ["mypy-boto3-translate (==1.17.3.0)"] -waf = ["mypy-boto3-waf (==1.17.3.0)"] -waf-regional = ["mypy-boto3-waf-regional (==1.17.3.0)"] -wafv2 = ["mypy-boto3-wafv2 (==1.17.3.0)"] -wellarchitected = ["mypy-boto3-wellarchitected (==1.17.3.0)"] -workdocs = ["mypy-boto3-workdocs (==1.17.3.0)"] -worklink = ["mypy-boto3-worklink (==1.17.3.0)"] -workmail = ["mypy-boto3-workmail (==1.17.3.0)"] -workmailmessageflow = ["mypy-boto3-workmailmessageflow (==1.17.3.0)"] -workspaces = ["mypy-boto3-workspaces (==1.17.3.0)"] -xray = ["mypy-boto3-xray (==1.17.3.0)"] +accessanalyzer = ["mypy-boto3-accessanalyzer (==1.17.7.0)"] +acm = ["mypy-boto3-acm (==1.17.7.0)"] +acm-pca = ["mypy-boto3-acm-pca (==1.17.7.0)"] +alexaforbusiness = ["mypy-boto3-alexaforbusiness (==1.17.7.0)"] +all = ["mypy-boto3-accessanalyzer (==1.17.7.0)", "mypy-boto3-acm (==1.17.7.0)", "mypy-boto3-acm-pca (==1.17.7.0)", "mypy-boto3-alexaforbusiness (==1.17.7.0)", "mypy-boto3-amp (==1.17.7.0)", "mypy-boto3-amplify (==1.17.7.0)", "mypy-boto3-amplifybackend (==1.17.7.0)", "mypy-boto3-apigateway (==1.17.7.0)", "mypy-boto3-apigatewaymanagementapi (==1.17.7.0)", "mypy-boto3-apigatewayv2 (==1.17.7.0)", "mypy-boto3-appconfig (==1.17.7.0)", "mypy-boto3-appflow (==1.17.7.0)", "mypy-boto3-appintegrations (==1.17.7.0)", "mypy-boto3-application-autoscaling (==1.17.7.0)", "mypy-boto3-application-insights (==1.17.7.0)", "mypy-boto3-appmesh (==1.17.7.0)", "mypy-boto3-appstream (==1.17.7.0)", "mypy-boto3-appsync (==1.17.7.0)", "mypy-boto3-athena (==1.17.7.0)", "mypy-boto3-auditmanager (==1.17.7.0)", "mypy-boto3-autoscaling (==1.17.7.0)", "mypy-boto3-autoscaling-plans (==1.17.7.0)", "mypy-boto3-backup (==1.17.7.0)", "mypy-boto3-batch (==1.17.7.0)", "mypy-boto3-braket (==1.17.7.0)", "mypy-boto3-budgets (==1.17.7.0)", "mypy-boto3-ce (==1.17.7.0)", "mypy-boto3-chime (==1.17.7.0)", "mypy-boto3-cloud9 (==1.17.7.0)", "mypy-boto3-clouddirectory (==1.17.7.0)", "mypy-boto3-cloudformation (==1.17.7.0)", "mypy-boto3-cloudfront (==1.17.7.0)", "mypy-boto3-cloudhsm (==1.17.7.0)", "mypy-boto3-cloudhsmv2 (==1.17.7.0)", "mypy-boto3-cloudsearch (==1.17.7.0)", "mypy-boto3-cloudsearchdomain (==1.17.7.0)", "mypy-boto3-cloudtrail (==1.17.7.0)", "mypy-boto3-cloudwatch (==1.17.7.0)", "mypy-boto3-codeartifact (==1.17.7.0)", "mypy-boto3-codebuild (==1.17.7.0)", "mypy-boto3-codecommit (==1.17.7.0)", "mypy-boto3-codedeploy (==1.17.7.0)", "mypy-boto3-codeguru-reviewer (==1.17.7.0)", "mypy-boto3-codeguruprofiler (==1.17.7.0)", "mypy-boto3-codepipeline (==1.17.7.0)", "mypy-boto3-codestar (==1.17.7.0)", "mypy-boto3-codestar-connections (==1.17.7.0)", "mypy-boto3-codestar-notifications (==1.17.7.0)", "mypy-boto3-cognito-identity (==1.17.7.0)", "mypy-boto3-cognito-idp (==1.17.7.0)", "mypy-boto3-cognito-sync (==1.17.7.0)", "mypy-boto3-comprehend (==1.17.7.0)", "mypy-boto3-comprehendmedical (==1.17.7.0)", "mypy-boto3-compute-optimizer (==1.17.7.0)", "mypy-boto3-config (==1.17.7.0)", "mypy-boto3-connect (==1.17.7.0)", "mypy-boto3-connect-contact-lens (==1.17.7.0)", "mypy-boto3-connectparticipant (==1.17.7.0)", "mypy-boto3-cur (==1.17.7.0)", "mypy-boto3-customer-profiles (==1.17.7.0)", "mypy-boto3-databrew (==1.17.7.0)", "mypy-boto3-dataexchange (==1.17.7.0)", "mypy-boto3-datapipeline (==1.17.7.0)", "mypy-boto3-datasync (==1.17.7.0)", "mypy-boto3-dax (==1.17.7.0)", "mypy-boto3-detective (==1.17.7.0)", "mypy-boto3-devicefarm (==1.17.7.0)", "mypy-boto3-devops-guru (==1.17.7.0)", "mypy-boto3-directconnect (==1.17.7.0)", "mypy-boto3-discovery (==1.17.7.0)", "mypy-boto3-dlm (==1.17.7.0)", "mypy-boto3-dms (==1.17.7.0)", "mypy-boto3-docdb (==1.17.7.0)", "mypy-boto3-ds (==1.17.7.0)", "mypy-boto3-dynamodb (==1.17.7.0)", "mypy-boto3-dynamodbstreams (==1.17.7.0)", "mypy-boto3-ebs (==1.17.7.0)", "mypy-boto3-ec2 (==1.17.7.0)", "mypy-boto3-ec2-instance-connect (==1.17.7.0)", "mypy-boto3-ecr (==1.17.7.0)", "mypy-boto3-ecr-public (==1.17.7.0)", "mypy-boto3-ecs (==1.17.7.0)", "mypy-boto3-efs (==1.17.7.0)", "mypy-boto3-eks (==1.17.7.0)", "mypy-boto3-elastic-inference (==1.17.7.0)", "mypy-boto3-elasticache (==1.17.7.0)", "mypy-boto3-elasticbeanstalk (==1.17.7.0)", "mypy-boto3-elastictranscoder (==1.17.7.0)", "mypy-boto3-elb (==1.17.7.0)", "mypy-boto3-elbv2 (==1.17.7.0)", "mypy-boto3-emr (==1.17.7.0)", "mypy-boto3-emr-containers (==1.17.7.0)", "mypy-boto3-es (==1.17.7.0)", "mypy-boto3-events (==1.17.7.0)", "mypy-boto3-firehose (==1.17.7.0)", "mypy-boto3-fms (==1.17.7.0)", "mypy-boto3-forecast (==1.17.7.0)", "mypy-boto3-forecastquery (==1.17.7.0)", "mypy-boto3-frauddetector (==1.17.7.0)", "mypy-boto3-fsx (==1.17.7.0)", "mypy-boto3-gamelift (==1.17.7.0)", "mypy-boto3-glacier (==1.17.7.0)", "mypy-boto3-globalaccelerator (==1.17.7.0)", "mypy-boto3-glue (==1.17.7.0)", "mypy-boto3-greengrass (==1.17.7.0)", "mypy-boto3-greengrassv2 (==1.17.7.0)", "mypy-boto3-groundstation (==1.17.7.0)", "mypy-boto3-guardduty (==1.17.7.0)", "mypy-boto3-health (==1.17.7.0)", "mypy-boto3-healthlake (==1.17.7.0)", "mypy-boto3-honeycode (==1.17.7.0)", "mypy-boto3-iam (==1.17.7.0)", "mypy-boto3-identitystore (==1.17.7.0)", "mypy-boto3-imagebuilder (==1.17.7.0)", "mypy-boto3-importexport (==1.17.7.0)", "mypy-boto3-inspector (==1.17.7.0)", "mypy-boto3-iot (==1.17.7.0)", "mypy-boto3-iot-data (==1.17.7.0)", "mypy-boto3-iot-jobs-data (==1.17.7.0)", "mypy-boto3-iot1click-devices (==1.17.7.0)", "mypy-boto3-iot1click-projects (==1.17.7.0)", "mypy-boto3-iotanalytics (==1.17.7.0)", "mypy-boto3-iotdeviceadvisor (==1.17.7.0)", "mypy-boto3-iotevents (==1.17.7.0)", "mypy-boto3-iotevents-data (==1.17.7.0)", "mypy-boto3-iotfleethub (==1.17.7.0)", "mypy-boto3-iotsecuretunneling (==1.17.7.0)", "mypy-boto3-iotsitewise (==1.17.7.0)", "mypy-boto3-iotthingsgraph (==1.17.7.0)", "mypy-boto3-iotwireless (==1.17.7.0)", "mypy-boto3-ivs (==1.17.7.0)", "mypy-boto3-kafka (==1.17.7.0)", "mypy-boto3-kendra (==1.17.7.0)", "mypy-boto3-kinesis (==1.17.7.0)", "mypy-boto3-kinesis-video-archived-media (==1.17.7.0)", "mypy-boto3-kinesis-video-media (==1.17.7.0)", "mypy-boto3-kinesis-video-signaling (==1.17.7.0)", "mypy-boto3-kinesisanalytics (==1.17.7.0)", "mypy-boto3-kinesisanalyticsv2 (==1.17.7.0)", "mypy-boto3-kinesisvideo (==1.17.7.0)", "mypy-boto3-kms (==1.17.7.0)", "mypy-boto3-lakeformation (==1.17.7.0)", "mypy-boto3-lambda (==1.17.7.0)", "mypy-boto3-lex-models (==1.17.7.0)", "mypy-boto3-lex-runtime (==1.17.7.0)", "mypy-boto3-lexv2-models (==1.17.7.0)", "mypy-boto3-lexv2-runtime (==1.17.7.0)", "mypy-boto3-license-manager (==1.17.7.0)", "mypy-boto3-lightsail (==1.17.7.0)", "mypy-boto3-location (==1.17.7.0)", "mypy-boto3-logs (==1.17.7.0)", "mypy-boto3-lookoutvision (==1.17.7.0)", "mypy-boto3-machinelearning (==1.17.7.0)", "mypy-boto3-macie (==1.17.7.0)", "mypy-boto3-macie2 (==1.17.7.0)", "mypy-boto3-managedblockchain (==1.17.7.0)", "mypy-boto3-marketplace-catalog (==1.17.7.0)", "mypy-boto3-marketplace-entitlement (==1.17.7.0)", "mypy-boto3-marketplacecommerceanalytics (==1.17.7.0)", "mypy-boto3-mediaconnect (==1.17.7.0)", "mypy-boto3-mediaconvert (==1.17.7.0)", "mypy-boto3-medialive (==1.17.7.0)", "mypy-boto3-mediapackage (==1.17.7.0)", "mypy-boto3-mediapackage-vod (==1.17.7.0)", "mypy-boto3-mediastore (==1.17.7.0)", "mypy-boto3-mediastore-data (==1.17.7.0)", "mypy-boto3-mediatailor (==1.17.7.0)", "mypy-boto3-meteringmarketplace (==1.17.7.0)", "mypy-boto3-mgh (==1.17.7.0)", "mypy-boto3-migrationhub-config (==1.17.7.0)", "mypy-boto3-mobile (==1.17.7.0)", "mypy-boto3-mq (==1.17.7.0)", "mypy-boto3-mturk (==1.17.7.0)", "mypy-boto3-mwaa (==1.17.7.0)", "mypy-boto3-neptune (==1.17.7.0)", "mypy-boto3-network-firewall (==1.17.7.0)", "mypy-boto3-networkmanager (==1.17.7.0)", "mypy-boto3-opsworks (==1.17.7.0)", "mypy-boto3-opsworkscm (==1.17.7.0)", "mypy-boto3-organizations (==1.17.7.0)", "mypy-boto3-outposts (==1.17.7.0)", "mypy-boto3-personalize (==1.17.7.0)", "mypy-boto3-personalize-events (==1.17.7.0)", "mypy-boto3-personalize-runtime (==1.17.7.0)", "mypy-boto3-pi (==1.17.7.0)", "mypy-boto3-pinpoint (==1.17.7.0)", "mypy-boto3-pinpoint-email (==1.17.7.0)", "mypy-boto3-pinpoint-sms-voice (==1.17.7.0)", "mypy-boto3-polly (==1.17.7.0)", "mypy-boto3-pricing (==1.17.7.0)", "mypy-boto3-qldb (==1.17.7.0)", "mypy-boto3-qldb-session (==1.17.7.0)", "mypy-boto3-quicksight (==1.17.7.0)", "mypy-boto3-ram (==1.17.7.0)", "mypy-boto3-rds (==1.17.7.0)", "mypy-boto3-rds-data (==1.17.7.0)", "mypy-boto3-redshift (==1.17.7.0)", "mypy-boto3-redshift-data (==1.17.7.0)", "mypy-boto3-rekognition (==1.17.7.0)", "mypy-boto3-resource-groups (==1.17.7.0)", "mypy-boto3-resourcegroupstaggingapi (==1.17.7.0)", "mypy-boto3-robomaker (==1.17.7.0)", "mypy-boto3-route53 (==1.17.7.0)", "mypy-boto3-route53domains (==1.17.7.0)", "mypy-boto3-route53resolver (==1.17.7.0)", "mypy-boto3-s3 (==1.17.7.0)", "mypy-boto3-s3control (==1.17.7.0)", "mypy-boto3-s3outposts (==1.17.7.0)", "mypy-boto3-sagemaker (==1.17.7.0)", "mypy-boto3-sagemaker-a2i-runtime (==1.17.7.0)", "mypy-boto3-sagemaker-edge (==1.17.7.0)", "mypy-boto3-sagemaker-featurestore-runtime (==1.17.7.0)", "mypy-boto3-sagemaker-runtime (==1.17.7.0)", "mypy-boto3-savingsplans (==1.17.7.0)", "mypy-boto3-schemas (==1.17.7.0)", "mypy-boto3-sdb (==1.17.7.0)", "mypy-boto3-secretsmanager (==1.17.7.0)", "mypy-boto3-securityhub (==1.17.7.0)", "mypy-boto3-serverlessrepo (==1.17.7.0)", "mypy-boto3-service-quotas (==1.17.7.0)", "mypy-boto3-servicecatalog (==1.17.7.0)", "mypy-boto3-servicecatalog-appregistry (==1.17.7.0)", "mypy-boto3-servicediscovery (==1.17.7.0)", "mypy-boto3-ses (==1.17.7.0)", "mypy-boto3-sesv2 (==1.17.7.0)", "mypy-boto3-shield (==1.17.7.0)", "mypy-boto3-signer (==1.17.7.0)", "mypy-boto3-sms (==1.17.7.0)", "mypy-boto3-sms-voice (==1.17.7.0)", "mypy-boto3-snowball (==1.17.7.0)", "mypy-boto3-sns (==1.17.7.0)", "mypy-boto3-sqs (==1.17.7.0)", "mypy-boto3-ssm (==1.17.7.0)", "mypy-boto3-sso (==1.17.7.0)", "mypy-boto3-sso-admin (==1.17.7.0)", "mypy-boto3-sso-oidc (==1.17.7.0)", "mypy-boto3-stepfunctions (==1.17.7.0)", "mypy-boto3-storagegateway (==1.17.7.0)", "mypy-boto3-sts (==1.17.7.0)", "mypy-boto3-support (==1.17.7.0)", "mypy-boto3-swf (==1.17.7.0)", "mypy-boto3-synthetics (==1.17.7.0)", "mypy-boto3-textract (==1.17.7.0)", "mypy-boto3-timestream-query (==1.17.7.0)", "mypy-boto3-timestream-write (==1.17.7.0)", "mypy-boto3-transcribe (==1.17.7.0)", "mypy-boto3-transfer (==1.17.7.0)", "mypy-boto3-translate (==1.17.7.0)", "mypy-boto3-waf (==1.17.7.0)", "mypy-boto3-waf-regional (==1.17.7.0)", "mypy-boto3-wafv2 (==1.17.7.0)", "mypy-boto3-wellarchitected (==1.17.7.0)", "mypy-boto3-workdocs (==1.17.7.0)", "mypy-boto3-worklink (==1.17.7.0)", "mypy-boto3-workmail (==1.17.7.0)", "mypy-boto3-workmailmessageflow (==1.17.7.0)", "mypy-boto3-workspaces (==1.17.7.0)", "mypy-boto3-xray (==1.17.7.0)"] +amp = ["mypy-boto3-amp (==1.17.7.0)"] +amplify = ["mypy-boto3-amplify (==1.17.7.0)"] +amplifybackend = ["mypy-boto3-amplifybackend (==1.17.7.0)"] +apigateway = ["mypy-boto3-apigateway (==1.17.7.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (==1.17.7.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (==1.17.7.0)"] +appconfig = ["mypy-boto3-appconfig (==1.17.7.0)"] +appflow = ["mypy-boto3-appflow (==1.17.7.0)"] +appintegrations = ["mypy-boto3-appintegrations (==1.17.7.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (==1.17.7.0)"] +application-insights = ["mypy-boto3-application-insights (==1.17.7.0)"] +appmesh = ["mypy-boto3-appmesh (==1.17.7.0)"] +appstream = ["mypy-boto3-appstream (==1.17.7.0)"] +appsync = ["mypy-boto3-appsync (==1.17.7.0)"] +athena = ["mypy-boto3-athena (==1.17.7.0)"] +auditmanager = ["mypy-boto3-auditmanager (==1.17.7.0)"] +autoscaling = ["mypy-boto3-autoscaling (==1.17.7.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (==1.17.7.0)"] +backup = ["mypy-boto3-backup (==1.17.7.0)"] +batch = ["mypy-boto3-batch (==1.17.7.0)"] +braket = ["mypy-boto3-braket (==1.17.7.0)"] +budgets = ["mypy-boto3-budgets (==1.17.7.0)"] +ce = ["mypy-boto3-ce (==1.17.7.0)"] +chime = ["mypy-boto3-chime (==1.17.7.0)"] +cloud9 = ["mypy-boto3-cloud9 (==1.17.7.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (==1.17.7.0)"] +cloudformation = ["mypy-boto3-cloudformation (==1.17.7.0)"] +cloudfront = ["mypy-boto3-cloudfront (==1.17.7.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (==1.17.7.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (==1.17.7.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (==1.17.7.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (==1.17.7.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (==1.17.7.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (==1.17.7.0)"] +codeartifact = ["mypy-boto3-codeartifact (==1.17.7.0)"] +codebuild = ["mypy-boto3-codebuild (==1.17.7.0)"] +codecommit = ["mypy-boto3-codecommit (==1.17.7.0)"] +codedeploy = ["mypy-boto3-codedeploy (==1.17.7.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (==1.17.7.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (==1.17.7.0)"] +codepipeline = ["mypy-boto3-codepipeline (==1.17.7.0)"] +codestar = ["mypy-boto3-codestar (==1.17.7.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (==1.17.7.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (==1.17.7.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (==1.17.7.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (==1.17.7.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (==1.17.7.0)"] +comprehend = ["mypy-boto3-comprehend (==1.17.7.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (==1.17.7.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (==1.17.7.0)"] +config = ["mypy-boto3-config (==1.17.7.0)"] +connect = ["mypy-boto3-connect (==1.17.7.0)"] +connect-contact-lens = ["mypy-boto3-connect-contact-lens (==1.17.7.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (==1.17.7.0)"] +cur = ["mypy-boto3-cur (==1.17.7.0)"] +customer-profiles = ["mypy-boto3-customer-profiles (==1.17.7.0)"] +databrew = ["mypy-boto3-databrew (==1.17.7.0)"] +dataexchange = ["mypy-boto3-dataexchange (==1.17.7.0)"] +datapipeline = ["mypy-boto3-datapipeline (==1.17.7.0)"] +datasync = ["mypy-boto3-datasync (==1.17.7.0)"] +dax = ["mypy-boto3-dax (==1.17.7.0)"] +detective = ["mypy-boto3-detective (==1.17.7.0)"] +devicefarm = ["mypy-boto3-devicefarm (==1.17.7.0)"] +devops-guru = ["mypy-boto3-devops-guru (==1.17.7.0)"] +directconnect = ["mypy-boto3-directconnect (==1.17.7.0)"] +discovery = ["mypy-boto3-discovery (==1.17.7.0)"] +dlm = ["mypy-boto3-dlm (==1.17.7.0)"] +dms = ["mypy-boto3-dms (==1.17.7.0)"] +docdb = ["mypy-boto3-docdb (==1.17.7.0)"] +ds = ["mypy-boto3-ds (==1.17.7.0)"] +dynamodb = ["mypy-boto3-dynamodb (==1.17.7.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (==1.17.7.0)"] +ebs = ["mypy-boto3-ebs (==1.17.7.0)"] +ec2 = ["mypy-boto3-ec2 (==1.17.7.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (==1.17.7.0)"] +ecr = ["mypy-boto3-ecr (==1.17.7.0)"] +ecr-public = ["mypy-boto3-ecr-public (==1.17.7.0)"] +ecs = ["mypy-boto3-ecs (==1.17.7.0)"] +efs = ["mypy-boto3-efs (==1.17.7.0)"] +eks = ["mypy-boto3-eks (==1.17.7.0)"] +elastic-inference = ["mypy-boto3-elastic-inference (==1.17.7.0)"] +elasticache = ["mypy-boto3-elasticache (==1.17.7.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (==1.17.7.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (==1.17.7.0)"] +elb = ["mypy-boto3-elb (==1.17.7.0)"] +elbv2 = ["mypy-boto3-elbv2 (==1.17.7.0)"] +emr = ["mypy-boto3-emr (==1.17.7.0)"] +emr-containers = ["mypy-boto3-emr-containers (==1.17.7.0)"] +es = ["mypy-boto3-es (==1.17.7.0)"] +essential = ["mypy-boto3-cloudformation (==1.17.7.0)", "mypy-boto3-dynamodb (==1.17.7.0)", "mypy-boto3-ec2 (==1.17.7.0)", "mypy-boto3-lambda (==1.17.7.0)", "mypy-boto3-rds (==1.17.7.0)", "mypy-boto3-s3 (==1.17.7.0)", "mypy-boto3-sqs (==1.17.7.0)"] +events = ["mypy-boto3-events (==1.17.7.0)"] +firehose = ["mypy-boto3-firehose (==1.17.7.0)"] +fms = ["mypy-boto3-fms (==1.17.7.0)"] +forecast = ["mypy-boto3-forecast (==1.17.7.0)"] +forecastquery = ["mypy-boto3-forecastquery (==1.17.7.0)"] +frauddetector = ["mypy-boto3-frauddetector (==1.17.7.0)"] +fsx = ["mypy-boto3-fsx (==1.17.7.0)"] +gamelift = ["mypy-boto3-gamelift (==1.17.7.0)"] +glacier = ["mypy-boto3-glacier (==1.17.7.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (==1.17.7.0)"] +glue = ["mypy-boto3-glue (==1.17.7.0)"] +greengrass = ["mypy-boto3-greengrass (==1.17.7.0)"] +greengrassv2 = ["mypy-boto3-greengrassv2 (==1.17.7.0)"] +groundstation = ["mypy-boto3-groundstation (==1.17.7.0)"] +guardduty = ["mypy-boto3-guardduty (==1.17.7.0)"] +health = ["mypy-boto3-health (==1.17.7.0)"] +healthlake = ["mypy-boto3-healthlake (==1.17.7.0)"] +honeycode = ["mypy-boto3-honeycode (==1.17.7.0)"] +iam = ["mypy-boto3-iam (==1.17.7.0)"] +identitystore = ["mypy-boto3-identitystore (==1.17.7.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (==1.17.7.0)"] +importexport = ["mypy-boto3-importexport (==1.17.7.0)"] +inspector = ["mypy-boto3-inspector (==1.17.7.0)"] +iot = ["mypy-boto3-iot (==1.17.7.0)"] +iot-data = ["mypy-boto3-iot-data (==1.17.7.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (==1.17.7.0)"] +iot1click-devices = ["mypy-boto3-iot1click-devices (==1.17.7.0)"] +iot1click-projects = ["mypy-boto3-iot1click-projects (==1.17.7.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (==1.17.7.0)"] +iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (==1.17.7.0)"] +iotevents = ["mypy-boto3-iotevents (==1.17.7.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (==1.17.7.0)"] +iotfleethub = ["mypy-boto3-iotfleethub (==1.17.7.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (==1.17.7.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (==1.17.7.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (==1.17.7.0)"] +iotwireless = ["mypy-boto3-iotwireless (==1.17.7.0)"] +ivs = ["mypy-boto3-ivs (==1.17.7.0)"] +kafka = ["mypy-boto3-kafka (==1.17.7.0)"] +kendra = ["mypy-boto3-kendra (==1.17.7.0)"] +kinesis = ["mypy-boto3-kinesis (==1.17.7.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (==1.17.7.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (==1.17.7.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (==1.17.7.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (==1.17.7.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (==1.17.7.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (==1.17.7.0)"] +kms = ["mypy-boto3-kms (==1.17.7.0)"] +lakeformation = ["mypy-boto3-lakeformation (==1.17.7.0)"] +lambda = ["mypy-boto3-lambda (==1.17.7.0)"] +lex-models = ["mypy-boto3-lex-models (==1.17.7.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (==1.17.7.0)"] +lexv2-models = ["mypy-boto3-lexv2-models (==1.17.7.0)"] +lexv2-runtime = ["mypy-boto3-lexv2-runtime (==1.17.7.0)"] +license-manager = ["mypy-boto3-license-manager (==1.17.7.0)"] +lightsail = ["mypy-boto3-lightsail (==1.17.7.0)"] +location = ["mypy-boto3-location (==1.17.7.0)"] +logs = ["mypy-boto3-logs (==1.17.7.0)"] +lookoutvision = ["mypy-boto3-lookoutvision (==1.17.7.0)"] +machinelearning = ["mypy-boto3-machinelearning (==1.17.7.0)"] +macie = ["mypy-boto3-macie (==1.17.7.0)"] +macie2 = ["mypy-boto3-macie2 (==1.17.7.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (==1.17.7.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (==1.17.7.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (==1.17.7.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (==1.17.7.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (==1.17.7.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (==1.17.7.0)"] +medialive = ["mypy-boto3-medialive (==1.17.7.0)"] +mediapackage = ["mypy-boto3-mediapackage (==1.17.7.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (==1.17.7.0)"] +mediastore = ["mypy-boto3-mediastore (==1.17.7.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (==1.17.7.0)"] +mediatailor = ["mypy-boto3-mediatailor (==1.17.7.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (==1.17.7.0)"] +mgh = ["mypy-boto3-mgh (==1.17.7.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (==1.17.7.0)"] +mobile = ["mypy-boto3-mobile (==1.17.7.0)"] +mq = ["mypy-boto3-mq (==1.17.7.0)"] +mturk = ["mypy-boto3-mturk (==1.17.7.0)"] +mwaa = ["mypy-boto3-mwaa (==1.17.7.0)"] +neptune = ["mypy-boto3-neptune (==1.17.7.0)"] +network-firewall = ["mypy-boto3-network-firewall (==1.17.7.0)"] +networkmanager = ["mypy-boto3-networkmanager (==1.17.7.0)"] +opsworks = ["mypy-boto3-opsworks (==1.17.7.0)"] +opsworkscm = ["mypy-boto3-opsworkscm (==1.17.7.0)"] +organizations = ["mypy-boto3-organizations (==1.17.7.0)"] +outposts = ["mypy-boto3-outposts (==1.17.7.0)"] +personalize = ["mypy-boto3-personalize (==1.17.7.0)"] +personalize-events = ["mypy-boto3-personalize-events (==1.17.7.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (==1.17.7.0)"] +pi = ["mypy-boto3-pi (==1.17.7.0)"] +pinpoint = ["mypy-boto3-pinpoint (==1.17.7.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (==1.17.7.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (==1.17.7.0)"] +polly = ["mypy-boto3-polly (==1.17.7.0)"] +pricing = ["mypy-boto3-pricing (==1.17.7.0)"] +qldb = ["mypy-boto3-qldb (==1.17.7.0)"] +qldb-session = ["mypy-boto3-qldb-session (==1.17.7.0)"] +quicksight = ["mypy-boto3-quicksight (==1.17.7.0)"] +ram = ["mypy-boto3-ram (==1.17.7.0)"] +rds = ["mypy-boto3-rds (==1.17.7.0)"] +rds-data = ["mypy-boto3-rds-data (==1.17.7.0)"] +redshift = ["mypy-boto3-redshift (==1.17.7.0)"] +redshift-data = ["mypy-boto3-redshift-data (==1.17.7.0)"] +rekognition = ["mypy-boto3-rekognition (==1.17.7.0)"] +resource-groups = ["mypy-boto3-resource-groups (==1.17.7.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (==1.17.7.0)"] +robomaker = ["mypy-boto3-robomaker (==1.17.7.0)"] +route53 = ["mypy-boto3-route53 (==1.17.7.0)"] +route53domains = ["mypy-boto3-route53domains (==1.17.7.0)"] +route53resolver = ["mypy-boto3-route53resolver (==1.17.7.0)"] +s3 = ["mypy-boto3-s3 (==1.17.7.0)"] +s3control = ["mypy-boto3-s3control (==1.17.7.0)"] +s3outposts = ["mypy-boto3-s3outposts (==1.17.7.0)"] +sagemaker = ["mypy-boto3-sagemaker (==1.17.7.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (==1.17.7.0)"] +sagemaker-edge = ["mypy-boto3-sagemaker-edge (==1.17.7.0)"] +sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (==1.17.7.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (==1.17.7.0)"] +savingsplans = ["mypy-boto3-savingsplans (==1.17.7.0)"] +schemas = ["mypy-boto3-schemas (==1.17.7.0)"] +sdb = ["mypy-boto3-sdb (==1.17.7.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (==1.17.7.0)"] +securityhub = ["mypy-boto3-securityhub (==1.17.7.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (==1.17.7.0)"] +service-quotas = ["mypy-boto3-service-quotas (==1.17.7.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (==1.17.7.0)"] +servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (==1.17.7.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (==1.17.7.0)"] +ses = ["mypy-boto3-ses (==1.17.7.0)"] +sesv2 = ["mypy-boto3-sesv2 (==1.17.7.0)"] +shield = ["mypy-boto3-shield (==1.17.7.0)"] +signer = ["mypy-boto3-signer (==1.17.7.0)"] +sms = ["mypy-boto3-sms (==1.17.7.0)"] +sms-voice = ["mypy-boto3-sms-voice (==1.17.7.0)"] +snowball = ["mypy-boto3-snowball (==1.17.7.0)"] +sns = ["mypy-boto3-sns (==1.17.7.0)"] +sqs = ["mypy-boto3-sqs (==1.17.7.0)"] +ssm = ["mypy-boto3-ssm (==1.17.7.0)"] +sso = ["mypy-boto3-sso (==1.17.7.0)"] +sso-admin = ["mypy-boto3-sso-admin (==1.17.7.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (==1.17.7.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (==1.17.7.0)"] +storagegateway = ["mypy-boto3-storagegateway (==1.17.7.0)"] +sts = ["mypy-boto3-sts (==1.17.7.0)"] +support = ["mypy-boto3-support (==1.17.7.0)"] +swf = ["mypy-boto3-swf (==1.17.7.0)"] +synthetics = ["mypy-boto3-synthetics (==1.17.7.0)"] +textract = ["mypy-boto3-textract (==1.17.7.0)"] +timestream-query = ["mypy-boto3-timestream-query (==1.17.7.0)"] +timestream-write = ["mypy-boto3-timestream-write (==1.17.7.0)"] +transcribe = ["mypy-boto3-transcribe (==1.17.7.0)"] +transfer = ["mypy-boto3-transfer (==1.17.7.0)"] +translate = ["mypy-boto3-translate (==1.17.7.0)"] +waf = ["mypy-boto3-waf (==1.17.7.0)"] +waf-regional = ["mypy-boto3-waf-regional (==1.17.7.0)"] +wafv2 = ["mypy-boto3-wafv2 (==1.17.7.0)"] +wellarchitected = ["mypy-boto3-wellarchitected (==1.17.7.0)"] +workdocs = ["mypy-boto3-workdocs (==1.17.7.0)"] +worklink = ["mypy-boto3-worklink (==1.17.7.0)"] +workmail = ["mypy-boto3-workmail (==1.17.7.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (==1.17.7.0)"] +workspaces = ["mypy-boto3-workspaces (==1.17.7.0)"] +xray = ["mypy-boto3-xray (==1.17.7.0)"] [[package]] name = "botocore" -version = "1.20.1" +version = "1.20.7" description = "Low-level, data-driven core of boto 3." category = "main" optional = false @@ -369,7 +434,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.4" +version = "1.14.5" description = "Foreign Function Interface for Python calling C code." category = "dev" optional = false @@ -380,7 +445,7 @@ pycparser = "*" [[package]] name = "cfn-lint" -version = "0.44.6" +version = "0.44.7" description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved" category = "dev" optional = false @@ -388,6 +453,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] aws-sam-translator = ">=1.25.0" +importlib-resources = {version = ">=1.4,<4", markers = "python_version < \"3.7\" and python_version != \"3.4\""} jsonpatch = {version = "*", markers = "python_version != \"3.4\""} jsonschema = ">=3.0,<4.0" junit-xml = ">=1.9,<2.0" @@ -397,11 +463,19 @@ six = ">=1.11" [[package]] name = "chardet" -version = "4.0.0" +version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "*" + +[[package]] +name = "chevron" +version = "0.14.0" +description = "Mustache templating language renderer" +category = "dev" +optional = true +python-versions = "*" [[package]] name = "click" @@ -419,24 +493,57 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "cookiecutter" +version = "1.7.2" +description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +binaryornot = ">=0.4.4" +click = ">=7.0" +Jinja2 = "<3.0.0" +jinja2-time = ">=0.2.0" +MarkupSafe = "<2.0.0" +poyo = ">=0.5.0" +python-slugify = ">=4.0.0" +requests = ">=2.23.0" +six = ">=1.10" + [[package]] name = "cryptography" -version = "3.3.1" +version = "3.4.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +python-versions = ">=3.6" [package.dependencies] cffi = ">=1.12" -six = ">=1.4.1" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "dateparser" +version = "0.7.6" +description = "Date parsing library designed to parse dates from HTML pages" +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +python-dateutil = "*" +pytz = "*" +regex = "!=2019.02.19" +tzlocal = "*" [[package]] name = "decorator" @@ -448,14 +555,14 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*" [[package]] name = "docker" -version = "4.4.1" +version = "4.2.2" description = "A Python library for the Docker Engine API." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +pypiwin32 = {version = "223", markers = "sys_platform == \"win32\" and python_version >= \"3.6\""} requests = ">=2.14.2,<2.18.0 || >2.18.0" six = ">=1.4.0" websocket-client = ">=0.32.0" @@ -489,6 +596,25 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" +[[package]] +name = "flask" +version = "1.1.2" +description = "A simple framework for building complex web applications." +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +click = ">=5.1" +itsdangerous = ">=0.24" +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + [[package]] name = "future" version = "0.18.2" @@ -521,6 +647,28 @@ zipp = ">=0.5" docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +[[package]] +name = "importlib-resources" +version = "3.3.1" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx", "rst.linker", "jaraco.packaging"] + +[[package]] +name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "jinja2" version = "2.11.3" @@ -535,6 +683,18 @@ MarkupSafe = ">=0.23" [package.extras] i18n = ["Babel (>=0.8)"] +[[package]] +name = "jinja2-time" +version = "0.2.0" +description = "Jinja2 Extension for Dates and Times" +category = "dev" +optional = true +python-versions = "*" + +[package.dependencies] +arrow = "*" +jinja2 = "*" + [[package]] name = "jmespath" version = "0.10.0" @@ -564,7 +724,7 @@ jsonpointer = ">=1.9" [[package]] name = "jsonpickle" -version = "1.5.1" +version = "2.0.0" description = "Python library for serializing any arbitrary object graph into JSON" category = "dev" optional = false @@ -646,7 +806,7 @@ test = ["pytest (<5.4)", "pytest-cov"] [[package]] name = "more-itertools" -version = "8.6.0" +version = "8.7.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false @@ -718,8 +878,8 @@ dmypy = ["psutil (>=4.0)"] [[package]] name = "mypy-boto3-ebs" -version = "1.17.3.0" -description = "Type annotations for boto3.EBS 1.17.3 service, generated by mypy-boto3-buider 4.4.0" +version = "1.17.7.0" +description = "Type annotations for boto3.EBS 1.17.7 service, generated by mypy-boto3-buider 4.4.0" category = "dev" optional = false python-versions = ">=3.6" @@ -729,8 +889,8 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "mypy-boto3-ec2" -version = "1.17.3.0" -description = "Type annotations for boto3.EC2 1.17.3 service, generated by mypy-boto3-buider 4.4.0" +version = "1.17.7.0" +description = "Type annotations for boto3.EC2 1.17.7 service, generated by mypy-boto3-buider 4.4.0" category = "dev" optional = false python-versions = ">=3.6" @@ -781,6 +941,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "pathtools" +version = "0.1.2" +description = "File system general utilities" +category = "dev" +optional = true +python-versions = "*" + [[package]] name = "pluggy" version = "0.13.1" @@ -795,6 +963,14 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +[[package]] +name = "poyo" +version = "0.5.0" +description = "A lightweight YAML Parser for Python. 🐓" +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "py" version = "1.10.0" @@ -843,6 +1019,17 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "pypiwin32" +version = "223" +description = "" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pywin32 = ">=223" + [[package]] name = "pyrsistent" version = "0.17.3" @@ -876,11 +1063,11 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.0" description = "Extensions to the standard Python datetime module" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [package.dependencies] six = ">=1.5" @@ -905,6 +1092,20 @@ cryptography = ["cryptography"] pycrypto = ["pycrypto (>=2.6.0,<2.7.0)", "pyasn1"] pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"] +[[package]] +name = "python-slugify" +version = "4.0.1" +description = "A Python Slugify application that handles Unicode" +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + [[package]] name = "pytz" version = "2021.1" @@ -915,7 +1116,7 @@ python-versions = "*" [[package]] name = "pywin32" -version = "227" +version = "300" description = "Python for Window Extensions" category = "dev" optional = false @@ -929,9 +1130,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = true +python-versions = "*" + [[package]] name = "requests" -version = "2.25.1" +version = "2.23.0" description = "Python HTTP for Humans." category = "dev" optional = false @@ -939,9 +1148,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" +chardet = ">=3.0.2,<4" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] @@ -985,6 +1194,19 @@ python-versions = "*" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" +[[package]] +name = "serverlessrepo" +version = "0.1.10" +description = "A Python library with convenience helpers for working with the AWS Serverless Application Repository." +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +boto3 = ">=1.9.56,<2.0" +pyyaml = ">=5.1,<6.0" +six = ">=1.11,<2.0" + [[package]] name = "six" version = "1.15.0" @@ -993,6 +1215,22 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +category = "dev" +optional = true +python-versions = "*" + +[[package]] +name = "tomlkit" +version = "0.7.0" +description = "Style preserving TOML library" +category = "dev" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "typed-ast" version = "1.4.2" @@ -1026,9 +1264,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "tzlocal" +version = "2.1" +description = "tzinfo object for the local timezone" +category = "dev" +optional = true +python-versions = "*" + +[package.dependencies] +pytz = "*" + [[package]] name = "urllib3" -version = "1.26.3" +version = "1.25.11" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1039,6 +1288,20 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "watchdog" +version = "0.10.3" +description = "Filesystem events monitoring" +category = "dev" +optional = true +python-versions = "*" + +[package.dependencies] +pathtools = ">=0.1.1" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] + [[package]] name = "wcwidth" version = "0.2.5" @@ -1100,13 +1363,18 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [extras] cli = ["typer"] +scannerd = [] [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "0497aea3eb5b606eaff5a4d166185511ba69c4f6c794adc5c256fdf8fe5d1d39" +python-versions = "^3.6" +content-hash = "cd57efec4577df4aa9c71c90e578f6ab815e05abc62f040da68371f7b4953bc2" [metadata.files] +arrow = [ + {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, + {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -1115,6 +1383,15 @@ attrs = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] +aws-lambda-builders = [ + {file = "aws_lambda_builders-1.2.0-py2-none-any.whl", hash = "sha256:b96656a27cea0663cf514a1e61c4bf450185608829b16abe0f852dd282ba31f8"}, + {file = "aws_lambda_builders-1.2.0-py3-none-any.whl", hash = "sha256:b48a84473cdb6c63d9d63aeeab036b34b8001f33fa39e3c9b6392442972194c3"}, + {file = "aws_lambda_builders-1.2.0.tar.gz", hash = "sha256:da4c65583c4ce6dd9b8124ff0db271b5f2a15d3f96d20c0695bacd693fa7cf61"}, +] +aws-sam-cli = [ + {file = "aws-sam-cli-1.18.1.tar.gz", hash = "sha256:6f8b2bb1198910280b8f4a92d7f0a4e662550bfe79a3c7603ce065919da0fd84"}, + {file = "aws_sam_cli-1.18.1-py3-none-any.whl", hash = "sha256:d58f7f035d5bb92707cc40738f190259484e6cfd72df318f3612ec2c63306878"}, +] aws-sam-translator = [ {file = "aws-sam-translator-1.34.0.tar.gz", hash = "sha256:857c62a03e3bb4a3f7074e867f52ced636dc06192c8216e00732122f21a206c2"}, {file = "aws_sam_translator-1.34.0-py2-none-any.whl", hash = "sha256:a22d505ddc0c48e3cf0ff0127096bdc1231d20c852509201ffba47dc8e683a1a"}, @@ -1124,72 +1401,80 @@ aws-xray-sdk = [ {file = "aws-xray-sdk-2.6.0.tar.gz", hash = "sha256:abf5b90f740e1f402e23414c9670e59cb9772e235e271fef2bce62b9100cbc77"}, {file = "aws_xray_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:076f7c610cd3564bbba3507d43e328fb6ff4a2e841d3590f39b2c3ce99d41e1d"}, ] +binaryornot = [ + {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, + {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, +] boto = [ {file = "boto-2.49.0-py2.py3-none-any.whl", hash = "sha256:147758d41ae7240dc989f0039f27da8ca0d53734be0eb869ef16e3adcfa462e8"}, {file = "boto-2.49.0.tar.gz", hash = "sha256:ea0d3b40a2d852767be77ca343b58a9e3a4b00d9db440efb8da74b4e58025e5a"}, ] boto3 = [ - {file = "boto3-1.17.1-py2.py3-none-any.whl", hash = "sha256:290a59324b66f982df502d3408026662238176fd9c3b2e15259fa0d2a459af14"}, - {file = "boto3-1.17.1.tar.gz", hash = "sha256:58a440f4c96a1f2f076577c7085237854ba6db41a7c91e4a33fb30068f8d4692"}, + {file = "boto3-1.17.7-py2.py3-none-any.whl", hash = "sha256:8ecc9c8c13355a2ae0446a227d67bf011affba4cab644240a3dc6ca23978e583"}, + {file = "boto3-1.17.7.tar.gz", hash = "sha256:56fad4485ef79bee64ad0d913c19e41b838a1cd6aefc935afefd7f393a0f0169"}, ] boto3-stubs = [ - {file = "boto3-stubs-1.17.3.0.tar.gz", hash = "sha256:c26c98203f94eb463ab0f13372e092ba605218002643a0915cf31cb53fcd957f"}, - {file = "boto3_stubs-1.17.3.0-py3-none-any.whl", hash = "sha256:54cd69236abe4742f90cb8b45ed76cbc8ba7885cf020d6d48b91fd3f554ba6ee"}, + {file = "boto3-stubs-1.17.7.0.tar.gz", hash = "sha256:5d47b6729d7aadb1cb4a9146031da3c597882bae4b1d619bc2111d2b3547ff3f"}, + {file = "boto3_stubs-1.17.7.0-py3-none-any.whl", hash = "sha256:cc9f833727d610d3ddc54b94e11ed6552e4204626b372d37479c08f854fb5b3f"}, ] botocore = [ - {file = "botocore-1.20.1-py2.py3-none-any.whl", hash = "sha256:e533b1da7af2e4d69d314375ec7052d95271a878a6040c3af1facd59bfbb7f3b"}, - {file = "botocore-1.20.1.tar.gz", hash = "sha256:9488ba35e2d4d17375f67b8fd300df5ec2f8317a2924032901009c999d563b59"}, + {file = "botocore-1.20.7-py2.py3-none-any.whl", hash = "sha256:77e34194eb4f916d9caaf4b518a8b5d73ddbba87bf951cdb20141a6fafc9ccbe"}, + {file = "botocore-1.20.7.tar.gz", hash = "sha256:27ea4a11f5cca9d655cb9ba462ad4a2bb28eb44d061b49e15a3239c040298ed2"}, ] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cffi = [ - {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, - {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, - {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, - {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, - {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, - {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, - {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, - {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, - {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, - {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, - {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, - {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, - {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, - {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, - {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, - {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"}, - {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, - {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, - {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ] cfn-lint = [ - {file = "cfn-lint-0.44.6.tar.gz", hash = "sha256:dec1fd133c419e8e9ba56ac2906dd385ddd5fc6b76e1c28db276c078096fd75c"}, - {file = "cfn_lint-0.44.6-py3-none-any.whl", hash = "sha256:52ce84ab8266138d6997544aa91206399a9b59a456715a16f2d63f7dbef8e2a2"}, + {file = "cfn-lint-0.44.7.tar.gz", hash = "sha256:c2e441602a1b0d15df6b2790e8a5755bbb71598e3b71feb52a73c0116b0f3059"}, + {file = "cfn_lint-0.44.7-py3-none-any.whl", hash = "sha256:5e45033599e193299bf99132149c559c9e2bde8f82385cb1037d5c570b03fc5c"}, ] chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +chevron = [ + {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, + {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1199,29 +1484,30 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +cookiecutter = [ + {file = "cookiecutter-1.7.2-py2.py3-none-any.whl", hash = "sha256:430eb882d028afb6102c084bab6cf41f6559a77ce9b18dc6802e3bc0cc5f4a30"}, + {file = "cookiecutter-1.7.2.tar.gz", hash = "sha256:efb6b2d4780feda8908a873e38f0e61778c23f6a2ea58215723bcceb5b515dac"}, +] cryptography = [ - {file = "cryptography-3.3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030"}, - {file = "cryptography-3.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0"}, - {file = "cryptography-3.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812"}, - {file = "cryptography-3.3.1-cp27-cp27m-win32.whl", hash = "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e"}, - {file = "cryptography-3.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901"}, - {file = "cryptography-3.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d"}, - {file = "cryptography-3.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5"}, - {file = "cryptography-3.3.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c"}, - {file = "cryptography-3.3.1-cp36-abi3-win32.whl", hash = "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a"}, - {file = "cryptography-3.3.1-cp36-abi3-win_amd64.whl", hash = "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7"}, - {file = "cryptography-3.3.1.tar.gz", hash = "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6"}, + {file = "cryptography-3.4.5-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:18d6f3ac1da14a01c95f4590ee58e96aa6628d231ce738e9eca330b9997127b6"}, + {file = "cryptography-3.4.5-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:c8dc9859c5a046e1ca22da360dfd609c09064a4f974881cb5cba977c581088ec"}, + {file = "cryptography-3.4.5-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:0bf49d5b38e4f3745a0eab0597fa97720dd49b30d65f534b49a82e303f149deb"}, + {file = "cryptography-3.4.5-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:9c6f7552d4f2130542d488b9d9e5b1546204b5d1aa90c823d50cce8eed421363"}, + {file = "cryptography-3.4.5-cp36-abi3-win32.whl", hash = "sha256:b0873ac0c0e6bc6882cd285930cc382ec4e78786be71bdc113c06246eea61294"}, + {file = "cryptography-3.4.5-cp36-abi3-win_amd64.whl", hash = "sha256:8b3b79af57e12aabbc3db81e563eaa07870293a1ffdcc891d107035ce9a0dbff"}, + {file = "cryptography-3.4.5.tar.gz", hash = "sha256:4f6761a82b51fe02cda8f45af1c2f698a10f50003dc9c2572d8a49eda2e6d35b"}, +] +dateparser = [ + {file = "dateparser-0.7.6-py2.py3-none-any.whl", hash = "sha256:7552c994f893b5cb8fcf103b4cd2ff7f57aab9bfd2619fdf0cf571c0740fd90b"}, + {file = "dateparser-0.7.6.tar.gz", hash = "sha256:e875efd8c57c85c2d02b238239878db59ff1971f5a823457fcc69e493bf6ebfa"}, ] decorator = [ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, ] docker = [ - {file = "docker-4.4.1-py2.py3-none-any.whl", hash = "sha256:e455fa49aabd4f22da9f4e1c1f9d16308286adc60abaf64bf3e1feafaed81d06"}, - {file = "docker-4.4.1.tar.gz", hash = "sha256:0604a74719d5d2de438753934b755bfcda6f62f49b8e4b30969a4b0a2a8a1220"}, + {file = "docker-4.2.2-py2.py3-none-any.whl", hash = "sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab"}, + {file = "docker-4.2.2.tar.gz", hash = "sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54"}, ] ecdsa = [ {file = "ecdsa-0.14.1-py2.py3-none-any.whl", hash = "sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe"}, @@ -1231,6 +1517,10 @@ flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] +flask = [ + {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, + {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] @@ -1242,10 +1532,22 @@ importlib-metadata = [ {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, ] +importlib-resources = [ + {file = "importlib_resources-3.3.1-py2.py3-none-any.whl", hash = "sha256:42068585cc5e8c2bf0a17449817401102a5125cbfbb26bb0f43cde1568f6f2df"}, + {file = "importlib_resources-3.3.1.tar.gz", hash = "sha256:0ed250dbd291947d1a298e89f39afcc477d5a6624770503034b72588601bcc05"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] +jinja2-time = [ + {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, + {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, +] jmespath = [ {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, @@ -1258,8 +1560,8 @@ jsonpatch = [ {file = "jsonpatch-1.28.tar.gz", hash = "sha256:e930adc932e4d36087dbbf0f22e1ded32185dfb20662f2e3dd848677a5295a14"}, ] jsonpickle = [ - {file = "jsonpickle-1.5.1-py2.py3-none-any.whl", hash = "sha256:8eb8323f0e12cb40687f0445e2115d8165901e20ac670add55bb53a95c68c0e5"}, - {file = "jsonpickle-1.5.1.tar.gz", hash = "sha256:060f97096559d1b86aa16cac2f4ea5f7b6da0c15d8a4de150b78013a886f9a51"}, + {file = "jsonpickle-2.0.0-py2.py3-none-any.whl", hash = "sha256:c1010994c1fbda87a48f8a56698605b598cb0fc6bb7e7927559fc1100e69aeac"}, + {file = "jsonpickle-2.0.0.tar.gz", hash = "sha256:0be49cba80ea6f87a168aa8168d717d00c6ca07ba83df3cec32d3b30bfe6fb9a"}, ] jsonpointer = [ {file = "jsonpointer-2.0-py2.py3-none-any.whl", hash = "sha256:ff379fa021d1b81ab539f5ec467c7745beb1a5671463f9dcc2b2d458bd361c1e"}, @@ -1335,8 +1637,8 @@ mock = [ {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, ] more-itertools = [ - {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, - {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, + {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, + {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, ] moto = [ {file = "moto-1.3.16-py2.py3-none-any.whl", hash = "sha256:f51903b6b532f6c887b111b3343f6925b77eef0505a914138d98290cf3526df9"}, @@ -1367,12 +1669,12 @@ mypy = [ {file = "mypy-0.800.tar.gz", hash = "sha256:e0202e37756ed09daf4b0ba64ad2c245d357659e014c3f51d8cd0681ba66940a"}, ] mypy-boto3-ebs = [ - {file = "mypy-boto3-ebs-1.17.3.0.tar.gz", hash = "sha256:d149dbbe7f80f8df6a56c8f33e0b493f2e0a283ce2f4c1cf6b78afb072b0ebfb"}, - {file = "mypy_boto3_ebs-1.17.3.0-py3-none-any.whl", hash = "sha256:c8a90dda32d66da8519279e54759bd68ff403b3613fe7e408f635e69fbce3d7b"}, + {file = "mypy-boto3-ebs-1.17.7.0.tar.gz", hash = "sha256:34e6c5be1322081fe4f17cb426c28f011619e10aff505e0d5a08f992bec5500c"}, + {file = "mypy_boto3_ebs-1.17.7.0-py3-none-any.whl", hash = "sha256:ebf13b146a3d4247c86d293a304b47e5e9c3c8f31c0154e04ce572a40c8f391a"}, ] mypy-boto3-ec2 = [ - {file = "mypy-boto3-ec2-1.17.3.0.tar.gz", hash = "sha256:b9416b78b47756fb916768bec09f76d5ec1658dd7f01c5f0386572899be47c65"}, - {file = "mypy_boto3_ec2-1.17.3.0-py3-none-any.whl", hash = "sha256:4b5b6fb82dfd04070d228855e259912a9af507841d6702989a023354567eab67"}, + {file = "mypy-boto3-ec2-1.17.7.0.tar.gz", hash = "sha256:2f6aa6db21fd3c8d8ac648ad5033afff919232d35705eddf67891014fe5d1ed7"}, + {file = "mypy_boto3_ec2-1.17.7.0-py3-none-any.whl", hash = "sha256:5487a655700800d5099de35a491bb7bdf9d27f9fc544c3750ed3a2c48bbf4279"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1386,10 +1688,17 @@ packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] +pathtools = [ + {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] +poyo = [ + {file = "poyo-0.5.0-py2.py3-none-any.whl", hash = "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a"}, + {file = "poyo-0.5.0.tar.gz", hash = "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd"}, +] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, @@ -1425,6 +1734,10 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +pypiwin32 = [ + {file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"}, + {file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"}, +] pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] @@ -1433,30 +1746,31 @@ pytest = [ {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, + {file = "python-dateutil-2.8.0.tar.gz", hash = "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"}, + {file = "python_dateutil-2.8.0-py2.py3-none-any.whl", hash = "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb"}, ] python-jose = [ {file = "python-jose-3.2.0.tar.gz", hash = "sha256:4e4192402e100b5fb09de5a8ea6bcc39c36ad4526341c123d401e2561720335b"}, {file = "python_jose-3.2.0-py2.py3-none-any.whl", hash = "sha256:67d7dfff599df676b04a996520d9be90d6cdb7e6dd10b4c7cacc0c3e2e92f2be"}, ] +python-slugify = [ + {file = "python-slugify-4.0.1.tar.gz", hash = "sha256:69a517766e00c1268e5bbfc0d010a0a8508de0b18d30ad5a1ff357f8ae724270"}, +] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] pywin32 = [ - {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, - {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, - {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, - {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, - {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, - {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, - {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, - {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, - {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, - {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, - {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, - {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, + {file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"}, + {file = "pywin32-300-cp35-cp35m-win_amd64.whl", hash = "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64"}, + {file = "pywin32-300-cp36-cp36m-win32.whl", hash = "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7"}, + {file = "pywin32-300-cp36-cp36m-win_amd64.whl", hash = "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85"}, + {file = "pywin32-300-cp37-cp37m-win32.whl", hash = "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc"}, + {file = "pywin32-300-cp37-cp37m-win_amd64.whl", hash = "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d"}, + {file = "pywin32-300-cp38-cp38-win32.whl", hash = "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae"}, + {file = "pywin32-300-cp38-cp38-win_amd64.whl", hash = "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190"}, + {file = "pywin32-300-cp39-cp39-win32.whl", hash = "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50"}, + {file = "pywin32-300-cp39-cp39-win_amd64.whl", hash = "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, @@ -1481,9 +1795,53 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] +regex = [ + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, +] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.23.0-py2.7.egg", hash = "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4"}, + {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, + {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, ] responses = [ {file = "responses-0.12.1-py2.py3-none-any.whl", hash = "sha256:ef265bd3200bdef5ec17912fc64a23570ba23597fd54ca75c18650fa1699213d"}, @@ -1497,10 +1855,22 @@ s3transfer = [ {file = "s3transfer-0.3.4-py2.py3-none-any.whl", hash = "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed"}, {file = "s3transfer-0.3.4.tar.gz", hash = "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2"}, ] +serverlessrepo = [ + {file = "serverlessrepo-0.1.10-py2.py3-none-any.whl", hash = "sha256:b99c69be8ce87ccc48103fbe371ba7b148c3374c57862e59118c402522e5ed52"}, + {file = "serverlessrepo-0.1.10.tar.gz", hash = "sha256:671f48038123f121437b717ed51f253a55775590f00fbab6fbc6a01f8d05c017"}, +] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] +text-unidecode = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] +tomlkit = [ + {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, + {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, +] typed-ast = [ {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, @@ -1542,9 +1912,16 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] +tzlocal = [ + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, +] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, + {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, +] +watchdog = [ + {file = "watchdog-0.10.3.tar.gz", hash = "sha256:4214e1379d128b0588021880ccaf40317ee156d4603ac388b9adcf29165e0c04"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/pyproject.toml b/pyproject.toml index 4496608..2ee3e35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,12 @@ dsnap = "dsnap.main:app" [tool.poetry.dependencies] python = "^3.6" -boto3 = "^1.16.63" +boto3 = "^1.16.0" typer = { version = "^0.3.2", optional = true } [tool.poetry.extras] cli = ["typer"] +scannerd = ["cfn-lint", "aws-sam-cli"] [tool.poetry.dev-dependencies] pytest = "^5.2" @@ -22,6 +23,8 @@ flake8 = "^3.8.4" mypy = "^0.800" boto3-stubs = {extras = ["ebs", "ec2"], version = "^1.16.63"} moto = {extras = ["ebs", "ec2"], version = "^1.3.16"} +cfn-lint = {version = "^0.44.7", optional = true} +aws-sam-cli = {version = "^1.18.1", optional = true} [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/scan.sh b/scan.sh new file mode 100755 index 0000000..fd8cbeb --- /dev/null +++ b/scan.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +strings snap-*.img \ + | tee >(grep -A1 -E "aws_access_key_id = ((?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})" 1>&2) \ + >/dev/null diff --git a/tests/test_aws.py b/tests/test_aws.py index ac2a1bf..f905b2c 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -23,7 +23,8 @@ def session(aws_credentials): @pytest.fixture(scope='function') def boto_conf(aws_credentials): - yield botocore.config.Config() + with mock_iam(): + yield botocore.config.Config() @pytest.fixture(scope='function') def ec2(session): diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 66e09ba..e649496 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -3,6 +3,7 @@ import mock import pytest from _pytest import capture +from moto import mock_iam, mock_ec2 from dsnap import prompt from .test_aws import session, boto_conf, aws_credentials # noqa: F401 @@ -10,12 +11,19 @@ def test_snap_id_from_input__unknown(session, capsys): with pytest.raises(UserWarning, match='^unknown'): - prompt.snap_id_from_input(session, "unknown-test") + prompt.snap_from_input(session, "unknown-test") +@mock_ec2 +def test_snap_id_from_input__none_no_instances(session): + with pytest.raises(UserWarning, match='no items'): + prompt.snap_from_input(session, None) + +@mock_ec2 def test_snap_id_from_input__none(session): - prompt.full_prompt = mock.MagicMock(return_value="none-test") - assert "none-test" == prompt.snap_id_from_input(session, None) + resp = mock.MagicMock(return_value="none-test") + prompt.resource_prompt = mock.MagicMock(return_value=resp) + assert resp == prompt.snap_from_input(session, None) def test_snap_id_from_input__snap(session): - assert "snap-test" == prompt.snap_id_from_input(session, "snap-test") + assert "snap-test" == prompt.snap_from_input(session, "snap-test").snapshot_id diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py index c524473..91ccfae 100644 --- a/tests/test_snapshot.py +++ b/tests/test_snapshot.py @@ -3,6 +3,7 @@ import pytest from botocore.response import StreamingBody +from mypy_boto3_ebs.type_defs import BlockTypeDef from dsnap import snapshot as s @@ -10,10 +11,10 @@ @pytest.fixture(scope='function') -def snapshot(session, boto_conf, tmp_path: Path): - snap = s.Snapshot('test-snapshot', session, boto_conf) +def local_snapshot(session, boto_conf, tmp_path: Path): + snap = s.LocalSnapshot('./test-snapshot.img', 'test-snapshot', session, boto_conf) - snap.output_file = str((tmp_path / 'test.img').absolute()) + snap.path = str((tmp_path / 'test.img').absolute()) # EBS API isn't supported by moto yet, so mock this manually snap.get_blocks = lambda: print('Mocked') @@ -23,62 +24,66 @@ def snapshot(session, boto_conf, tmp_path: Path): return snap -def test_snapshot_id(snapshot: s.Snapshot): - snapshot.snapshot_id = 'test-snapshot' +def test_snapshot_id(local_snapshot: s.LocalSnapshot): + local_snapshot.snapshot_id = 'test-snapshot' @pytest.fixture(scope='function') -def truncate(snapshot: s.Snapshot, tmp_path: Path): - snapshot.truncate() +def truncate(local_snapshot: s.LocalSnapshot, tmp_path: Path): + local_snapshot.truncate() return tmp_path / 'test.img' -def test_truncate(truncate: str): +def test_truncate(truncate: Path): assert truncate.stat().st_size == s.MEGABYTE assert truncate.read_bytes().startswith(b'\x00\x00\x00\x00\x00\x00\x00') assert truncate.read_bytes().endswith(b'\x00\x00\x00\x00\x00\x00\x00') @pytest.fixture(scope='function') -def block(truncate, snapshot): +def block(truncate, local_snapshot: s.LocalSnapshot): body = b'test1234' - return s.Block( - BlockData=StreamingBody(BytesIO(body), len(body)), - Offset=0, - Checksum='k36NX7tIvUlJU2zWW401xCa4DS+DDFwwjizexCKuIkQ=', - ) + b = s.Block(local_snapshot, BlockTypeDef( + BlockIndex=0, + BlockToken="token", + )) + b.BlockData = BytesIO(body) + b.Checksum = "k36NX7tIvUlJU2zWW401xCa4DS+DDFwwjizexCKuIkQ=" + return b @pytest.fixture(scope='function') -def write_block(block: s.Block, snapshot: s.Snapshot): - written = snapshot._write_block(block) +def write_block(block: s.Block, local_snapshot: s.LocalSnapshot): + written = block.write() assert written == 8 - return snapshot + return local_snapshot def test_write_block(write_block: s.Snapshot): - with open(write_block.output_file, 'rb') as f: + with open(write_block.path, 'rb') as f: assert f.read().startswith(b'test1234\x00\x00') @pytest.fixture(scope='function') -def block_offset(truncate, snapshot): +def block_offset(truncate, local_snapshot: s.LocalSnapshot): body = b'test1234' - return s.Block( - BlockData=StreamingBody(BytesIO(body), len(body)), - Offset=524288, # Equivalent to BlockIndex 1 - Checksum='k36NX7tIvUlJU2zWW401xCa4DS+DDFwwjizexCKuIkQ=', - ) + b = s.Block(local_snapshot, BlockTypeDef( + BlockIndex=1, + BlockToken="token", + )) + b.BlockData = BytesIO(body) + b.Checksum = "k36NX7tIvUlJU2zWW401xCa4DS+DDFwwjizexCKuIkQ=" + return b @pytest.fixture(scope='function') -def write_block_offset(block_offset: s.Block, snapshot: s.Snapshot): - written = snapshot._write_block(block_offset) +def write_block_offset(block_offset: s.Block, local_snapshot: s.LocalSnapshot): + written = block_offset.write() assert written == 8 - return snapshot + return local_snapshot def test_write_block_offset(write_block_offset: s.Snapshot): - with open(write_block_offset.output_file, 'rb') as f: + with open(write_block_offset.path, 'rb') as f: f.seek(524288) assert f.read().startswith(b'test1234\x00\x00')