├── .gitignore ├── media ├── local_demo.gif ├── architecture.png ├── deployed_demo.gif └── zappa_deploy_all.png ├── pyproject.toml ├── proxy.py ├── zappa_settings.json ├── README.md └── poetry.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | .DS_Store -------------------------------------------------------------------------------- /media/local_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-whitestone/python-proxy-server/HEAD/media/local_demo.gif -------------------------------------------------------------------------------- /media/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-whitestone/python-proxy-server/HEAD/media/architecture.png -------------------------------------------------------------------------------- /media/deployed_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-whitestone/python-proxy-server/HEAD/media/deployed_demo.gif -------------------------------------------------------------------------------- /media/zappa_deploy_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-whitestone/python-proxy-server/HEAD/media/zappa_deploy_all.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "python-proxy-server" 3 | version = "0.1.0" 4 | description = "A free, Python proxy server on AWS lambda" 5 | authors = ["Your Name "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | requests = "^2.23.0" 11 | zappa = "^0.51.0" 12 | flask = "^1.1.1" 13 | 14 | [tool.poetry.dev-dependencies] 15 | 16 | [build-system] 17 | requires = ["poetry>=0.12"] 18 | build-backend = "poetry.masonry.api" 19 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | import io 2 | import pickle 3 | 4 | from flask import Flask, request, send_file 5 | import requests 6 | 7 | app = Flask(__name__) 8 | 9 | 10 | @app.route("/", methods=["GET", "POST"]) 11 | def index(): 12 | data = request.form 13 | r = requests.get(data["url"]) 14 | pickled_response = pickle.dumps(r) 15 | return send_file(io.BytesIO(pickled_response), mimetype="application/octet-stream") 16 | 17 | 18 | if __name__ == "__main__": 19 | app.run(debug=True) 20 | -------------------------------------------------------------------------------- /zappa_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy_us_east_1": { 3 | "app_function": "proxy.app", 4 | "aws_region": "us-east-1", 5 | "project_name": "proxy", 6 | "runtime": "python3.8", 7 | "exclude": ["*.png", "*.gif"] 8 | }, 9 | "proxy_us_west_1": { 10 | "app_function": "proxy.app", 11 | "aws_region": "us-west-1", 12 | "project_name": "proxy", 13 | "runtime": "python3.8", 14 | "exclude": ["*.png", "*.gif"] 15 | } 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Proxy Server 2 | 3 | Amazon Web Services' (AWS) serverless offering, [AWS Lambda](https://aws.amazon.com/lambda/), is part of their "always free tier". What that means is you get 1 million requests per month, or 3.2 million seconds of compute time per month, for free. Forever. 4 | 5 | You can deploy a simple Flask app on Lambda, which will make your web requests for you from within AWS' network, rather than your local, or web scraping machine's IP address. This can help you get around firewalls, or websites that will block your IP address after repeated requests. The deployment is seamlessly handled by [Zappa](https://github.com/Miserlou/Zappa), a framework for managing serverless Python applications. 6 | 7 |

8 | 9 |

10 | 11 | Check out the [accompanying blog post](https://ianwhitestone.work/free-python-proxy-server/) for more info. 12 | 13 | ## Setup 14 | 15 | 1. Clone repo 16 | 2. Run `poetry install --no-root` from the root of the repo to create a new virtual environment with the three project dependencies; [zappa](https://github.com/Miserlou/Zappa), [requests](https://github.com/psf/requests) and [flask](https://github.com/pallets/flask). If you don't have poetry install, you can install it as per the instructions [here](https://python-poetry.org/docs/#osx-linux-bashonwindows-install-instructions). Alternatively, you can use [pipenv](https://github.com/pypa/pipenv) or manually create a [virtual environment](https://docs.python.org/3/tutorial/venv.html) with the three aforementioned dependencies. 17 | 3. **[optionally]** Configure the `zappa_settings.json` 18 | 19 | Out of the box, this repo will deploy two proxy servers, one in `us-east-1` and one in `us-west-1`. 20 | 21 | ```json 22 | { 23 | "proxy_us_east_1": { 24 | "app_function": "proxy.app", 25 | "aws_region": "us-east-1", 26 | "project_name": "proxy", 27 | "runtime": "python3.8", 28 | "exclude": ["*.png", "*.gif"] 29 | }, 30 | "proxy_us_west_1": { 31 | "app_function": "proxy.app", 32 | "aws_region": "us-west-1", 33 | "project_name": "proxy", 34 | "runtime": "python3.8", 35 | "exclude": ["*.png", "*.gif"] 36 | } 37 | } 38 | ``` 39 | 40 | Note, the `"exclude": ["*.png", "*.gif"]` setting prevents the files in the `media` folder of this repo from being included in the lambda deployment package. If you are working from scratch in your own repo, you can remove this. The zappa config can be easily modified to suit your needs, by removing or adding more lambda functions in other regions. See the [zappa docs](https://github.com/Miserlou/Zappa#advanced-settings/) for a full list of settings than can be configured in `zappa_settings.json`. 41 | 42 | 4. Deploy all lambda functions by running `zappa deploy --all` 43 | 44 | 45 | 46 | 5. Make requests through the proxy, and load the pickled response: 47 | 48 | ```python 49 | import pickle 50 | import requests 51 | 52 | # Be sure to grab the API gateway URLs outputted by **your** zappa deploy call... 53 | PROXY_EAST = 'https://ripodibd96.execute-api.us-east-1.amazonaws.com/proxy_us_east_1' 54 | PROXY_WEST = 'https://0c9ngekah5.execute-api.us-west-1.amazonaws.com/proxy_us_west_1' 55 | 56 | proxy_response = requests.post( 57 | PROXY_EAST, 58 | data={'url': 'https://ianwhitestone.work'} 59 | ) 60 | 61 | if not proxy_response.ok: 62 | raise Exception( 63 | "Proxy request not successful. Status code: " 64 | f"{proxy_response.status_code}\n{proxy_response.text}" 65 | ) 66 | response = pickle.loads(proxy_response.content) 67 | ``` 68 | 69 | You can now interact with the requests response object as you would normally: 70 | 71 | ```python 72 | >>> response 73 | 74 | >>> response.text 75 | '\n\n \n Ian Whitestone\n\n ... 76 | ... 77 | ``` 78 | 79 | 80 | ## Demos 81 | 82 | ### Local demo 83 | 84 | Here's a demo of running the proxy server locally, with `python proxy.py`: 85 | 86 | 87 | 88 | ### Deployed demo 89 | 90 | After running `zappa deploy --all`, we can grab the URL for the proxy we want to use and make a post request against it: 91 | 92 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "main" 3 | description = "Bash tab completion for argparse" 4 | name = "argcomplete" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.11.1" 8 | 9 | [package.extras] 10 | test = ["coverage", "flake8", "pexpect", "wheel"] 11 | 12 | [[package]] 13 | category = "main" 14 | description = "The AWS SDK for Python" 15 | name = "boto3" 16 | optional = false 17 | python-versions = "*" 18 | version = "1.12.26" 19 | 20 | [package.dependencies] 21 | botocore = ">=1.15.26,<1.16.0" 22 | jmespath = ">=0.7.1,<1.0.0" 23 | s3transfer = ">=0.3.0,<0.4.0" 24 | 25 | [[package]] 26 | category = "main" 27 | description = "Low-level, data-driven core of boto 3." 28 | name = "botocore" 29 | optional = false 30 | python-versions = "*" 31 | version = "1.15.26" 32 | 33 | [package.dependencies] 34 | docutils = ">=0.10,<0.16" 35 | jmespath = ">=0.7.1,<1.0.0" 36 | python-dateutil = ">=2.1,<3.0.0" 37 | 38 | [package.dependencies.urllib3] 39 | python = "<3.4.0 || >=3.5.0" 40 | version = ">=1.20,<1.26" 41 | 42 | [[package]] 43 | category = "main" 44 | description = "Python package for providing Mozilla's CA Bundle." 45 | name = "certifi" 46 | optional = false 47 | python-versions = "*" 48 | version = "2019.11.28" 49 | 50 | [[package]] 51 | category = "main" 52 | description = "Convert AWS CloudFormation templates between JSON and YAML formats" 53 | name = "cfn-flip" 54 | optional = false 55 | python-versions = "*" 56 | version = "1.2.2" 57 | 58 | [package.dependencies] 59 | Click = "*" 60 | PyYAML = ">=4.1" 61 | six = "*" 62 | 63 | [[package]] 64 | category = "main" 65 | description = "Universal encoding detector for Python 2 and 3" 66 | name = "chardet" 67 | optional = false 68 | python-versions = "*" 69 | version = "3.0.4" 70 | 71 | [[package]] 72 | category = "main" 73 | description = "Composable command line interface toolkit" 74 | name = "click" 75 | optional = false 76 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 77 | version = "7.1.1" 78 | 79 | [[package]] 80 | category = "main" 81 | description = "Docutils -- Python Documentation Utilities" 82 | name = "docutils" 83 | optional = false 84 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 85 | version = "0.15.2" 86 | 87 | [[package]] 88 | category = "main" 89 | description = "Module for converting between datetime.timedelta and Go's Duration strings." 90 | name = "durationpy" 91 | optional = false 92 | python-versions = "*" 93 | version = "0.5" 94 | 95 | [[package]] 96 | category = "main" 97 | description = "A simple framework for building complex web applications." 98 | name = "flask" 99 | optional = false 100 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 101 | version = "1.1.1" 102 | 103 | [package.dependencies] 104 | Jinja2 = ">=2.10.1" 105 | Werkzeug = ">=0.15" 106 | click = ">=5.1" 107 | itsdangerous = ">=0.24" 108 | 109 | [package.extras] 110 | dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] 111 | docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] 112 | dotenv = ["python-dotenv"] 113 | 114 | [[package]] 115 | category = "main" 116 | description = "Clean single-source support for Python 3 and 2" 117 | name = "future" 118 | optional = false 119 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 120 | version = "0.18.2" 121 | 122 | [[package]] 123 | category = "main" 124 | description = "Hjson, a user interface for JSON." 125 | name = "hjson" 126 | optional = false 127 | python-versions = "*" 128 | version = "3.0.1" 129 | 130 | [[package]] 131 | category = "main" 132 | description = "Internationalized Domain Names in Applications (IDNA)" 133 | name = "idna" 134 | optional = false 135 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 136 | version = "2.9" 137 | 138 | [[package]] 139 | category = "main" 140 | description = "Various helpers to pass data to untrusted environments and back." 141 | name = "itsdangerous" 142 | optional = false 143 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 144 | version = "1.1.0" 145 | 146 | [[package]] 147 | category = "main" 148 | description = "A very fast and expressive template engine." 149 | name = "jinja2" 150 | optional = false 151 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 152 | version = "2.11.1" 153 | 154 | [package.dependencies] 155 | MarkupSafe = ">=0.23" 156 | 157 | [package.extras] 158 | i18n = ["Babel (>=0.8)"] 159 | 160 | [[package]] 161 | category = "main" 162 | description = "JSON Matching Expressions" 163 | name = "jmespath" 164 | optional = false 165 | python-versions = "*" 166 | version = "0.9.5" 167 | 168 | [[package]] 169 | category = "main" 170 | description = "A CLI tool for AWS Lambda developers" 171 | name = "kappa" 172 | optional = false 173 | python-versions = "*" 174 | version = "0.6.0" 175 | 176 | [package.dependencies] 177 | PyYAML = ">=3.11" 178 | boto3 = ">=1.2.3" 179 | click = ">=5.1" 180 | placebo = ">=0.8.1" 181 | 182 | [[package]] 183 | category = "main" 184 | description = "Safely add untrusted strings to HTML/XML markup." 185 | name = "markupsafe" 186 | optional = false 187 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 188 | version = "1.1.1" 189 | 190 | [[package]] 191 | category = "main" 192 | description = "pip-tools keeps your pinned dependencies fresh." 193 | name = "pip-tools" 194 | optional = false 195 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 196 | version = "4.5.1" 197 | 198 | [package.dependencies] 199 | click = ">=7" 200 | six = "*" 201 | 202 | [[package]] 203 | category = "main" 204 | description = "Make boto3 calls that look real but have no effect" 205 | name = "placebo" 206 | optional = false 207 | python-versions = "*" 208 | version = "0.9.0" 209 | 210 | [[package]] 211 | category = "main" 212 | description = "Extensions to the standard Python datetime module" 213 | name = "python-dateutil" 214 | optional = false 215 | python-versions = "*" 216 | version = "2.6.1" 217 | 218 | [package.dependencies] 219 | six = ">=1.5" 220 | 221 | [[package]] 222 | category = "main" 223 | description = "A Python Slugify application that handles Unicode" 224 | name = "python-slugify" 225 | optional = false 226 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 227 | version = "4.0.0" 228 | 229 | [package.dependencies] 230 | text-unidecode = ">=1.3" 231 | 232 | [package.extras] 233 | unidecode = ["Unidecode (>=1.1.1)"] 234 | 235 | [[package]] 236 | category = "main" 237 | description = "YAML parser and emitter for Python" 238 | name = "pyyaml" 239 | optional = false 240 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 241 | version = "5.3.1" 242 | 243 | [[package]] 244 | category = "main" 245 | description = "Python HTTP for Humans." 246 | name = "requests" 247 | optional = false 248 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 249 | version = "2.23.0" 250 | 251 | [package.dependencies] 252 | certifi = ">=2017.4.17" 253 | chardet = ">=3.0.2,<4" 254 | idna = ">=2.5,<3" 255 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 256 | 257 | [package.extras] 258 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 259 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 260 | 261 | [[package]] 262 | category = "main" 263 | description = "An Amazon S3 Transfer Manager" 264 | name = "s3transfer" 265 | optional = false 266 | python-versions = "*" 267 | version = "0.3.3" 268 | 269 | [package.dependencies] 270 | botocore = ">=1.12.36,<2.0a.0" 271 | 272 | [[package]] 273 | category = "main" 274 | description = "Python 2 and 3 compatibility utilities" 275 | name = "six" 276 | optional = false 277 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 278 | version = "1.14.0" 279 | 280 | [[package]] 281 | category = "main" 282 | description = "The most basic Text::Unidecode port" 283 | name = "text-unidecode" 284 | optional = false 285 | python-versions = "*" 286 | version = "1.3" 287 | 288 | [[package]] 289 | category = "main" 290 | description = "Python Library for Tom's Obvious, Minimal Language" 291 | name = "toml" 292 | optional = false 293 | python-versions = "*" 294 | version = "0.10.0" 295 | 296 | [[package]] 297 | category = "main" 298 | description = "Fast, Extensible Progress Meter" 299 | name = "tqdm" 300 | optional = false 301 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 302 | version = "4.43.0" 303 | 304 | [package.extras] 305 | dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] 306 | 307 | [[package]] 308 | category = "main" 309 | description = "AWS CloudFormation creation library" 310 | name = "troposphere" 311 | optional = false 312 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 313 | version = "2.6.0" 314 | 315 | [package.dependencies] 316 | cfn_flip = ">=1.0.2" 317 | 318 | [package.extras] 319 | policy = ["awacs (>=0.8)"] 320 | 321 | [[package]] 322 | category = "main" 323 | description = "HTTP library with thread-safe connection pooling, file post, and more." 324 | name = "urllib3" 325 | optional = false 326 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 327 | version = "1.25.8" 328 | 329 | [package.extras] 330 | brotli = ["brotlipy (>=0.6.0)"] 331 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 332 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 333 | 334 | [[package]] 335 | category = "main" 336 | description = "The comprehensive WSGI web application library." 337 | name = "werkzeug" 338 | optional = false 339 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 340 | version = "0.16.1" 341 | 342 | [package.extras] 343 | dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] 344 | termcolor = ["termcolor"] 345 | watchdog = ["watchdog"] 346 | 347 | [[package]] 348 | category = "main" 349 | description = "A built-package format for Python" 350 | name = "wheel" 351 | optional = false 352 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 353 | version = "0.34.2" 354 | 355 | [package.extras] 356 | test = ["pytest (>=3.0.0)", "pytest-cov"] 357 | 358 | [[package]] 359 | category = "main" 360 | description = "Apache-like combined logging for WSGI Web Applications" 361 | name = "wsgi-request-logger" 362 | optional = false 363 | python-versions = "*" 364 | version = "0.4.6" 365 | 366 | [[package]] 367 | category = "main" 368 | description = "Server-less Python Web Services for AWS Lambda and API Gateway" 369 | name = "zappa" 370 | optional = false 371 | python-versions = "*" 372 | version = "0.51.0" 373 | 374 | [package.dependencies] 375 | PyYAML = "*" 376 | Werkzeug = "<1.0" 377 | argcomplete = "*" 378 | boto3 = "*" 379 | durationpy = "*" 380 | future = "*" 381 | hjson = "*" 382 | jmespath = "*" 383 | kappa = "0.6.0" 384 | pip = ">=9.0.1" 385 | pip-tools = "*" 386 | python-dateutil = "<2.7.0" 387 | python-slugify = "*" 388 | requests = ">=2.20.0" 389 | six = "*" 390 | toml = "*" 391 | tqdm = "*" 392 | troposphere = "*" 393 | wheel = "*" 394 | wsgi-request-logger = "*" 395 | 396 | [metadata] 397 | content-hash = "a871b5a39a85594be541ee26b09894b30d0f9d3efa4e1e43328638f6468bbc18" 398 | python-versions = "^3.8" 399 | 400 | [metadata.files] 401 | argcomplete = [ 402 | {file = "argcomplete-1.11.1-py2.py3-none-any.whl", hash = "sha256:890bdd1fcbb973ed73db241763e78b6d958580e588c2910b508c770a59ef37d7"}, 403 | {file = "argcomplete-1.11.1.tar.gz", hash = "sha256:5ae7b601be17bf38a749ec06aa07fb04e7b6b5fc17906948dc1866e7facf3740"}, 404 | ] 405 | boto3 = [ 406 | {file = "boto3-1.12.26-py2.py3-none-any.whl", hash = "sha256:934057d57331d1f9c969f186533e87f460e97d2127ca2fde0505a234ec9ad286"}, 407 | {file = "boto3-1.12.26.tar.gz", hash = "sha256:c998a5a8c6ec6ebb4f3fc6884faa0250e511dbc27fee555e422fe6023e0e73ac"}, 408 | ] 409 | botocore = [ 410 | {file = "botocore-1.15.26-py2.py3-none-any.whl", hash = "sha256:9a697a5e1fca536d68b13f99545a49f446db622c0c525f0d29609bb875ac0b7a"}, 411 | {file = "botocore-1.15.26.tar.gz", hash = "sha256:285bd8f8f2258628eb32994505570896c76bdd8f3c7828d2e7749739f9ba07a9"}, 412 | ] 413 | certifi = [ 414 | {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, 415 | {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, 416 | ] 417 | cfn-flip = [ 418 | {file = "cfn_flip-1.2.2-py3-none-any.whl", hash = "sha256:40fc5e279fd99ebebec164b2b9e6f7b76f22e0ae42b3b48e45f70010bd082619"}, 419 | {file = "cfn_flip-1.2.2.tar.gz", hash = "sha256:8ddd026cef53cd11a4a27e6dc239417a2bbf86587ba2e59fd6bf615ad8dec516"}, 420 | ] 421 | chardet = [ 422 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 423 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 424 | ] 425 | click = [ 426 | {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, 427 | {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, 428 | ] 429 | docutils = [ 430 | {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"}, 431 | {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"}, 432 | {file = "docutils-0.15.2.tar.gz", hash = "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"}, 433 | ] 434 | durationpy = [ 435 | {file = "durationpy-0.5.tar.gz", hash = "sha256:5ef9416b527b50d722f34655becfb75e49228eb82f87b855ed1911b3314b5408"}, 436 | ] 437 | flask = [ 438 | {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, 439 | {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, 440 | ] 441 | future = [ 442 | {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, 443 | ] 444 | hjson = [ 445 | {file = "hjson-3.0.1.tar.gz", hash = "sha256:1d1727faa6aaef2973921877125a3ab7c5f6d34b93233179d01770f41fab51f9"}, 446 | ] 447 | idna = [ 448 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, 449 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, 450 | ] 451 | itsdangerous = [ 452 | {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, 453 | {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, 454 | ] 455 | jinja2 = [ 456 | {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, 457 | {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, 458 | ] 459 | jmespath = [ 460 | {file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"}, 461 | {file = "jmespath-0.9.5.tar.gz", hash = "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9"}, 462 | ] 463 | kappa = [ 464 | {file = "kappa-0.6.0-py2-none-any.whl", hash = "sha256:4d6b7b3accce4a0aaaac92b36237a6304f0f2fffbbe3caea3f7c9f52d12c9989"}, 465 | {file = "kappa-0.6.0.tar.gz", hash = "sha256:4b5b372872f25d619e427e04282551048dc975a107385b076b3ffc6406a15833"}, 466 | ] 467 | markupsafe = [ 468 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 469 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 470 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 471 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 472 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 473 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 474 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 475 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 476 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 477 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 478 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 479 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 480 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 481 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 482 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 483 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 484 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 485 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 486 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 487 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 488 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 489 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 490 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 491 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 492 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 493 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 494 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 495 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 496 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 497 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 498 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 499 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 500 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 501 | ] 502 | pip-tools = [ 503 | {file = "pip-tools-4.5.1.tar.gz", hash = "sha256:693f30e451875796b1b25203247f0b4cf48a4c4a5ab7341f4f33ffd498cdcc98"}, 504 | {file = "pip_tools-4.5.1-py2.py3-none-any.whl", hash = "sha256:be9c796aa88b2eec5cabf1323ba1cb60a08212b84bfb75b8b4037a8ef8cb8cb6"}, 505 | ] 506 | placebo = [ 507 | {file = "placebo-0.9.0.tar.gz", hash = "sha256:03157f8527bbc2965b71b88f4a139ef8038618b346787f20d63e3c5da541b047"}, 508 | ] 509 | python-dateutil = [ 510 | {file = "python-dateutil-2.6.1.tar.gz", hash = "sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca"}, 511 | {file = "python_dateutil-2.6.1-py2.py3-none-any.whl", hash = "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c"}, 512 | ] 513 | python-slugify = [ 514 | {file = "python-slugify-4.0.0.tar.gz", hash = "sha256:a8fc3433821140e8f409a9831d13ae5deccd0b033d4744d94b31fea141bdd84c"}, 515 | ] 516 | pyyaml = [ 517 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 518 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 519 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 520 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 521 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 522 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 523 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 524 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 525 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 526 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 527 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 528 | ] 529 | requests = [ 530 | {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, 531 | {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, 532 | ] 533 | s3transfer = [ 534 | {file = "s3transfer-0.3.3-py2.py3-none-any.whl", hash = "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13"}, 535 | {file = "s3transfer-0.3.3.tar.gz", hash = "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"}, 536 | ] 537 | six = [ 538 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 539 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 540 | ] 541 | text-unidecode = [ 542 | {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, 543 | {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, 544 | ] 545 | toml = [ 546 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 547 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 548 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 549 | ] 550 | tqdm = [ 551 | {file = "tqdm-4.43.0-py2.py3-none-any.whl", hash = "sha256:0d8b5afb66e23d80433102e9bd8b5c8b65d34c2a2255b2de58d97bd2ea8170fd"}, 552 | {file = "tqdm-4.43.0.tar.gz", hash = "sha256:f35fb121bafa030bd94e74fcfd44f3c2830039a2ddef7fc87ef1c2d205237b24"}, 553 | ] 554 | troposphere = [ 555 | {file = "troposphere-2.6.0.tar.gz", hash = "sha256:3dbc8c438b9694a39035e29bbcb00fcd3890c9e24a6b938873d6e9afae6e4d9c"}, 556 | ] 557 | urllib3 = [ 558 | {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, 559 | {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, 560 | ] 561 | werkzeug = [ 562 | {file = "Werkzeug-0.16.1-py2.py3-none-any.whl", hash = "sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2"}, 563 | {file = "Werkzeug-0.16.1.tar.gz", hash = "sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"}, 564 | ] 565 | wheel = [ 566 | {file = "wheel-0.34.2-py2.py3-none-any.whl", hash = "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e"}, 567 | {file = "wheel-0.34.2.tar.gz", hash = "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96"}, 568 | ] 569 | wsgi-request-logger = [ 570 | {file = "wsgi-request-logger-0.4.6.tar.gz", hash = "sha256:445d7ec52799562f812006394d0b4a7064b37084c6ea6bd74ea7a2136c97ed83"}, 571 | ] 572 | zappa = [ 573 | {file = "zappa-0.51.0-py3-none-any.whl", hash = "sha256:c6e740334c1c39e644a345124b7317003420b632a8f7a6811d18b82bb16e2f8e"}, 574 | {file = "zappa-0.51.0.tar.gz", hash = "sha256:ccfc336d3bc48a6898cbbd157e16653f717dbe3ca37f933ce40acfe242a03a40"}, 575 | ] 576 | --------------------------------------------------------------------------------