├── .github └── workflows │ ├── publish-monte-carlo-pi-docker.yml │ └── publish-random-art-docker.yml ├── .gitignore ├── .idea ├── .gitignore ├── copyright │ └── profiles_settings.xml ├── deployment.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── markdown-navigator-enh.xml ├── markdown-navigator.xml ├── misc.xml ├── modules.xml ├── other.xml ├── scalable_docker_simulation.iml └── vcs.xml ├── LICENSE ├── README.md ├── __init__.py ├── docker_sim_manager.py ├── example_monte_carlo_pi.py ├── example_monte_carlo_pi ├── Dockerfile ├── README.md ├── __init__.py ├── img │ ├── pi_estmimate_n1000.png │ ├── pi_estmimate_n10000.png │ ├── pi_estmimate_n100000.png │ └── pi_estmimate_n500.png ├── monte_carlo_pi.py ├── poetry.lock ├── pyproject.toml └── test_monte_carlo_pi.py ├── example_random_art.py ├── example_random_art ├── Dockerfile ├── README.md ├── __init__.py ├── img │ ├── 15x15-30-5000.jpg │ ├── 15x15-5-5000.jpg │ └── 5x5-10-5000.jpg ├── poetry.lock ├── pyproject.toml └── sprithering.py ├── img ├── actions_menu_bar.png ├── concept.png ├── packages.png ├── packages_details.png ├── workflow_details.png └── workflows-overview.png ├── poetry.lock └── pyproject.toml /.github/workflows/publish-monte-carlo-pi-docker.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Monte Carlo Pi Test and Docker Publish 5 | 6 | on: 7 | push: 8 | paths: 9 | - 'example_monte_carlo_pi/**' 10 | workflow_dispatch: 11 | env: 12 | REGISTRY: ghcr.io 13 | IMAGE_NAME: ${{ github.repository }}/docker_simulation_pipeline_example/monte-carlo-pi-image 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-20.04 18 | strategy: 19 | matrix: 20 | python-version: [3.9] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install dependencies 29 | working-directory: example_monte_carlo_pi 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install flake8 33 | pip install pytest 34 | pip install poetry 35 | poetry config virtualenvs.create false 36 | poetry install 37 | - name: Lint with flake8 38 | run: | 39 | # stop the build if there are Python syntax errors or undefined names 40 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 41 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 42 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 43 | - name: Test with pytest 44 | run: | 45 | PYTHONPATH=. pytest 46 | 47 | build_docker_and_push_to_registry: 48 | needs: test 49 | runs-on: ubuntu-20.04 50 | permissions: 51 | contents: read 52 | packages: write 53 | steps: 54 | - name: Check out the repo 55 | uses: actions/checkout@v3 56 | - name: Set up QEMU 57 | uses: docker/setup-qemu-action@v1 58 | - name: Set up Docker Buildx 59 | uses: docker/setup-buildx-action@v1 60 | - name: Log in to the Container registry 61 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 62 | with: 63 | registry: ${{ env.REGISTRY }} 64 | username: ${{ github.actor }} 65 | password: ${{ secrets.GITHUB_TOKEN }} 66 | - name: Extract metadata (tags, labels) for Docker 67 | id: meta 68 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 69 | with: 70 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 71 | - name: Build and push docker image 72 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 73 | with: 74 | context: ./example_monte_carlo_pi/ 75 | file: example_monte_carlo_pi/Dockerfile 76 | platforms: linux/amd64,linux/arm64 77 | push: true 78 | tags: ${{ steps.meta.outputs.tags }} 79 | labels: ${{ steps.meta.outputs.labels }} 80 | -------------------------------------------------------------------------------- /.github/workflows/publish-random-art-docker.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Generative Art Docker Publish 5 | 6 | on: 7 | push: 8 | paths: 9 | - 'example_random_art/**' 10 | jobs: 11 | test: 12 | runs-on: ubuntu-20.04 13 | strategy: 14 | matrix: 15 | python-version: [3.9] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | working-directory: example_random_art 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install flake8 28 | pip install pytest 29 | pip install poetry 30 | poetry config virtualenvs.create false 31 | poetry install 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | 39 | build_docker_and_push_to_registry: 40 | needs: test 41 | runs-on: ubuntu-20.04 42 | steps: 43 | - name: Check out the repo 44 | uses: actions/checkout@v2 45 | - name: Set up QEMU 46 | uses: docker/setup-qemu-action@v1 47 | - name: Set up Docker Buildx 48 | uses: docker/setup-buildx-action@v1 49 | - name: Login to GitHub Container Registry 50 | uses: docker/login-action@v1 51 | with: 52 | registry: ghcr.io 53 | username: ${{ github.repository_owner }} 54 | password: ${{ secrets.CR_PAT }} 55 | - name: Build and push docker image 56 | uses: docker/build-push-action@v2 57 | with: 58 | context: ./example_random_art/ 59 | file: example_random_art/Dockerfile 60 | platforms: linux/amd64,linux/arm64 61 | push: true 62 | tags: ghcr.io/${{ github.repository_owner }}/docker_simulation_pipeline_example/random-art-image:latest 63 | labels: org.opencontainers.image.source=https://github.com/${{ github.repository }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | 142 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../../:\Users\gu92jih\PycharmProjects\scalable_docker_simulation\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 92 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/scalable_docker_simulation.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Michael Wittmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker simulation pipeline 2 | This is an example project to demonstrate how one can easily scale simulation runs with docker containers. 3 | 4 | ## Introduction 5 | 6 | ### Docker 7 | Docker is a great tool, which lets you orchestrate your software stack with scalable containers. 8 | It is mainly used to deploy applications on a distributed cloud infrastructure. 9 | An application once packed into a docker image is portable to every host running docker. 10 | There are some issues when you move across architecture (e.g. x86 to ARM), but most of you will still work on x86 architectures. 11 | For more information on docker please look out for some great tutorials at [docker.com](www.docker.com), [medium.com](www.medium.com) or [youtube.com](www.youtube.com) ... 12 | 13 | ### Docker for simulation tasks 14 | In simulation you often want to run the same simulation with different parameter sets or configurations. 15 | At a point in time, when the number of iterations and/or computation time grows, you might ask your self: "How can I scale up my simulation tasks". 16 | Maybe you have access to a cloud computing infrastructure, maybe you have a big computing machine at your lab, 17 | maybe you want to run your simulation on different hosts. Anyways, docker offers a great framework to scale up and orchestrate your simulation runs. 18 | 19 | In this tutorial, I'm gonna show you how to set up und run a simulation pipeline using docker to parallelize your simulation tasks. 20 | 21 | Note: In this tutorial I use 2 very simple examples to give you an idea of the main concept. Running those scripts in a docker container 22 | may look a bit over engineered - and it is! Before you adapt this approach, ask your self: "How long is my main computation time 23 | compared to the time it takes to startup the docker container?" 24 | 25 | ## What you need 26 | - Docker installed on your target machine (https://docs.docker.com/get-docker/) 27 | - Python 3.7 (or newer) installed your target machine (https://www.python.org/downloads/) 28 | - A GitHub user account (Needed to download docker images, from GitHubs container image registry) 29 | - Git installed on your target machine (https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 30 | - (An IDE/Text Editor (VSCode, PyCharm, SublimeText)) - if you want to play around 31 | 32 | 33 | ## Basic concept 34 | Our pipeline consists of three main parts: 35 | 1. A simulation, having a Dockerfile (e.g. `example_monte_carlo_pi`, `example_random_art`) 36 | 2. A GitHubActions pipeline, which test, builds and deploys your containerized simulation. 37 | 3. A Python script that orchestrates your simulation tasks in docker (`docker_sim_manager.py`) 38 | 39 | 40 | 41 | ## Simulations 42 | It doesn't matter what kind of simulation you are running. If it runs on your PC, you will be able to pack it into a docker image. 43 | For sure it might get more complex than in the example projects, but you only have to deal with it once. Just make sure that your simulation 44 | offers the possibility to parametrize it either via config-files or a cli. 45 | 46 | For this tutorial I prepared two simple example simulations in Python: `example_monte_carlo_pi`, `example_random_art` 47 | 48 | ## Deploy your simulation in a docker image 49 | 50 | ### Create a Dockerfile 51 | The blueprint of every docker image is a Dockerfile. It tells Docker how to build your image, 52 | which software gets installed, which environment variables are set and which program should be ran at start. 53 | 54 | Let's have a look at `example_random_art/Dockerfile`: 55 | 56 | - It uses an existing image based on debian buster, along with python 3.9 preinstalled. 57 | ```docker 58 | FROM python:3.9-buster 59 | ``` 60 | - TimeZone is set to `Europe/Berlin` 61 | ```docker 62 | ENV TZ=Europe/Berlin 63 | ``` 64 | 65 | - The working directory is set to `/usr/src/app` 66 | ```docker 67 | WORKDIR /usr/src/app` 68 | ``` 69 | - During build all files from the directory the Dockerfile lies in, get copied to the images working directory (in this case `usr/src/app` 70 | ```docker 71 | COPY ./* ./ 72 | ``` 73 | 74 | - The project dependencies get installed via `pip` and `poetry` 75 | ```docker 76 | RUN pip install --upgrade pip 77 | RUN pip install poetry 78 | RUN poetry config virtualenvs.create false \ 79 | && poetry install --no-interaction --no-ansi 80 | ``` 81 | 82 | - The images entrypoint is defined as 83 | ```docker 84 | ENTRYPOINT ["python", "./sprithering.py"] 85 | ``` 86 | 87 | Out of this blueprint you are able to build a docker image locally on your machine running: 88 | 89 | ```shell script 90 | docker build -t myimage:1.0 91 | ``` 92 | 93 | That's okay if you work on the same machine, where run your simulations, but gets annoying when you switch between different machines. 94 | Moreover, you would need to run `docker build` everytime you made changes on your source code. (Remember the simulation is packed into the container during its build. If you want apply changes you have to build a new version of your image) 95 | 96 | ### Test, Build and Deploy your Simulation with GitHub Actions 97 | Modern CI/CD pipelines let you automate those tasks and fill in seemingly into your workflow. (Like the `Dockerfile` you just have to set it up once) 98 | In this tutorial I will show you how to run such a pipeline with GitHub Actions (https://github.com/features/actions). 99 | The concept works also on other Platforms e.g. GitLab (https://docs.gitlab.com/ee/ci/). You will need to adapt the CI/CD pipeline to your requirements. 100 | 101 | If you haven't heard about CI/CD at all so far, I would recommend you to look for some tutorials out there. Especially a proper CI/CD pipeline 102 | with unit-tests can help you a lot with finding bugs in your code. 103 | 104 | #### GitHubActions 105 | GitHub Action jobs are collected in the directory `.github/` of your repository. GitHub will auto-check for those files on repository events. 106 | 107 | Let' have a look at `.github/publish-monte-carlo-pi-docker`: 108 | 109 | 110 | #### Configure action events 111 | We define this script to run on every file change in `simulation_monte_carlo/**` after any push event. 112 | 113 | ```yaml 114 | on: 115 | push: 116 | paths: 117 | - 'simulation_monte_carlo/**' 118 | ``` 119 | 120 | #### Specify jobs 121 | The action script contains two jobs: 122 | 1. `test` Run unit tests and code-format checkers (This ensures, that you hopefully deploy a working version of your simulation) 123 | ```yaml 124 | jobs: 125 | test: 126 | runs-on: ubuntu-latest 127 | strategy: 128 | matrix: 129 | python-version: [3.9] 130 | 131 | steps: 132 | - uses: actions/checkout@v2 133 | - name: Set up Python ${{ matrix.python-version }} 134 | uses: actions/setup-python@v2 135 | with: 136 | python-version: ${{ matrix.python-version }} 137 | - name: Install dependencies 138 | working-directory: example_monte_carlo_pi 139 | run: | 140 | python -m pip install --upgrade pip 141 | pip install flake8 142 | pip install pytest 143 | pip install poetry 144 | poetry config virtualenvs.create false 145 | poetry install 146 | - name: Lint with flake8 147 | run: | 148 | # stop the build if there are Python syntax errors or undefined names 149 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 150 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 151 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 152 | - name: Test with pytest 153 | run: | 154 | pytest 155 | ``` 156 | 157 | 2. `build_docker_and_push_to_registry` A Docker build and deploy task. This job runs only if the first one succeeds. 158 | This job checks out the current version of the repository, and runs docker build and push with the given parameters. 159 | - `username`: Docker Registry Username. In this case: GitHub environment variable for the user triggering the action script 160 | - `password`: Docker Registry Password. In this case: Your Personal Access Token (PAT) with sufficient rights on your repository 161 | - `dockerfile`: Path to your dockerfile inside the repository 162 | - `registry`: URL of your desired docker registry. In this case: GitHub Container Image Registry 163 | - `repository`: In this case: // 164 | - `tags`: Image tag. In this case: `latest` 165 | 166 | For more infos have a look at (https://github.com/docker/build-push-action) 167 | ```yaml 168 | build_docker_and_push_to_registry: 169 | needs: test 170 | runs-on: ubuntu-20.04 171 | steps: 172 | - name: Check out the repo 173 | uses: actions/checkout@v2 174 | - name: Set up QEMU 175 | uses: docker/setup-qemu-action@v1 176 | - name: Set up Docker Buildx 177 | uses: docker/setup-buildx-action@v1 178 | - name: Login to GitHub Container Registry 179 | uses: docker/login-action@v1 180 | with: 181 | registry: ghcr.io 182 | username: ${{ github.repository_owner }} 183 | password: ${{ secrets.CR_PAT }} 184 | - name: Build and push docker image 185 | uses: docker/build-push-action@v2 186 | with: 187 | context: ./example_monte_carlo_pi/ 188 | file: example_monte_carlo_pi/Dockerfile 189 | platforms: linux/amd64,linux/arm64,linux/386 190 | push: true 191 | tags: ghcr.io/${{ github.repository_owner }}/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 192 | ``` 193 | 194 | You can watch the results of your pipeline under `https://github.com/YOUR_NAMESPACE/YOUR_REPO/actions`: 195 | 196 | 197 | 198 | 199 | 200 | The images should now also be visible on your GitHub account (`https://github.com/?tab=packages`): 201 | 202 | 203 | 204 | 205 | 206 | 207 | ### Pull your docker image 208 | Let's verify that everything works properly, and let's pull the image to our local machine. 209 | 210 | 1. At the first time you must provide your login credentials for your container image registry 211 | ```shell script 212 | docker login ghcr.io 213 | ``` 214 | 2. Pull the image 215 | ```shell script 216 | docker pull ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 217 | ... 218 | >e44af8ed0266: Pull complete 219 | >Digest: sha256:77b65a3bb8deb5b4aa436d28a5dc7fc561b4882f2f115c3843b4afe1a7df23d4 220 | >Status: Downloaded newer image for ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 221 | >ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 222 | 223 | ``` 224 | 225 | 3. Run the container 226 | ```shell script 227 | docker run -it ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 228 | ... 229 | >Starting simulation: iterations=100000, random_seed=1 230 | >Result: pi_hat = 3.1378400000 231 | >Generation plot... 232 | >Simulation finished! (0.12351 ms) 233 | 234 | ``` 235 | 236 | Alright your simulation gets now automatically deployed in a docker container and is ready to be used for parallel simulations! 237 | 238 | 239 | 240 | ## Orchestrate your simulations 241 | That was a lot of work to setup the pipeline. Now its time to harvest the fruit of our efforts ;-). 242 | We are now able to pull the latest version of our tested simulation, and are ready to run as many parallel versions of it as we want. 243 | 244 | Therefore I wrote a small Python script which helps you to orchestrate your simulation jobs (`docker_sim_manager.py`) and two examples based on the previous container examples (`example_monte_carlo.py`, `example_random_art.py`) 245 | 246 | ### DockerSimManager 247 | I wont go through the code line for line, but I'll give you a compact overview of the tasks/elements of the script: 248 | - `DockerSimManager`: Main class, handling your docker containers. 249 | - `docker_container_url`: Simulation image URL at container image registry 250 | - `max_workers`: number of parallel workers 251 | - `data_directory`: path to the output directory on your host (This path gets mounted in your containers under `/mnt/data/`) 252 | - `docker_repo_tag`: container tag, Default: latest 253 | 254 | - `SimJob`: A object, which represents a distinct simulation job. You can pass paths to template files, give it a name and specify the initial command appended on the containers entrypoint. 255 | - `sim_Name`: Simulation name (must be unique) 256 | - `templates`: files to be copied from your host to the container 257 | - `command`: command to be appended at containers entry point 258 | 259 | - JobQueue: The `DockerSimManager` collects all your simulation tasks in a simple queue. Before run, you must submit `SimJobs` by adding them with `add_sim_job()` 260 | 261 | - `start_computation()`: Starts computation of all jobs inside the queue. Jobs must be added before calling this function 262 | 263 | - `_init_simulation()`: Prepares the simulation task 264 | 265 | - `_authenticate_at_container_registry`: Authenticates at container registry. (If you choose other registries than gitHub, modify this function) 266 | 267 | 268 | ### Run your simulations 269 | You can now run your simulation with 4 simple steps: 270 | 1. Specify an output folder on your host machine e.g. 271 | ```python 272 | # Choose output directory on your host's file system 273 | output_folder = Path.home().joinpath('example_docker_simulation') 274 | ``` 275 | 276 | 2. Create a DockerSimManager object. 277 | ```python 278 | # Generate DockerSimManager object. Specify simulation image, number of parallel containers and output_path 279 | docker_manager = DockerSimManager('ghcr.io/michaelwittmann/docker_simulation_pipeline_example/random-art-image', 280 | 10, 281 | output_folder) 282 | ``` 283 | 284 | 3. Add simulation jobs to the queue 285 | ```python 286 | # Add 20 simulation jobs to the job queue 287 | for i in range(1,10): 288 | docker_manager.add_sim_job(SimJob(f'randomArt_size{i}x{i}',None, command=f'-g {i} -i 30 -s 5000 -o /mnt/data -n 50')) 289 | 290 | for i in range(1,10): 291 | docker_manager.add_sim_job(SimJob(f'random_Art_invaders_{i}',None, command=f'-g 10 -i {i} -s 5000 -o /mnt/data -n 50')) 292 | ``` 293 | 294 | 4. Start computation 295 | ```python 296 | # Start computation 297 | docker_manager.start_computation() 298 | ``` 299 | 300 | ### Run the examples 301 | 302 | 1. Install requirements 303 | ``` 304 | pip install poetry 305 | poetry install 306 | ``` 307 | 308 | 2. Run the example scripts 309 | ``` 310 | python example_monte_carlo_pi.py 311 | ``` 312 | AND/OR 313 | ``` 314 | python example_ramdom_art.py 315 | ``` 316 | 317 | 3. Check the outputs 318 | 319 | Windows 320 | ``` 321 | dir %systemdrive%%homepath%\example_docker_simulation 322 | ``` 323 | 324 | Linux 325 | ``` 326 | ls ~/example_docker_simulation 327 | ``` 328 | 329 | 330 | ## Acknowledgements 331 | Thanks to [Maximilian Speicher](https://github.com/maxispeicher) for the inspiration on this tutorial, and the first implementation of `DockerSimManager` 332 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/__init__.py -------------------------------------------------------------------------------- /docker_sim_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Provides a light-weight framework to scale up simulations tasks with docker containers 3 | 4 | Disclaimer: This code is part of an example project. In order to reduce complexity, 5 | I decided to use a simple method and class design. There is still a massive potential in 6 | error handling and generalization of methods and classes. Feel free to use this code as a 7 | starting point. 8 | """ 9 | 10 | import datetime 11 | import shutil 12 | import threading 13 | import time 14 | from pathlib import Path 15 | import concurrent.futures 16 | import platform 17 | import dateutil 18 | import os 19 | import docker 20 | from docker.errors import DockerException, NotFound, APIError 21 | from docker.types import Mount, LogConfig 22 | from halo import Halo 23 | from loguru import logger 24 | from getpass import getpass 25 | 26 | __author__ = "Michael Wittmann and Maximilian Speicher" 27 | __copyright__ = "Copyright 2020, Michael Wittmann and Maximilian Speicher" 28 | 29 | __license__ = "MIT" 30 | __version__ = "1.0.0" 31 | __maintainer__ = "Michael Wittmann" 32 | __email__ = "michael.wittmann@tum.de" 33 | __status__ = "Example" 34 | 35 | 36 | class SimJob(): 37 | def __init__(self, sim_Name, templates:Path, command=None) -> None: 38 | """ 39 | Creates a distinct job 40 | :param sim_Name: Simulation name (must be unique) 41 | :param templates: files to be copied from your host to the container 42 | :param command: command to be appended at containers entry point 43 | """ 44 | self.templates = templates 45 | self.sim_name = sim_Name 46 | self.command = command 47 | 48 | def __str__(self) -> str: 49 | return self.sim_name 50 | 51 | 52 | class DockerSimManager(): 53 | def __init__(self, 54 | docker_container_url:str, 55 | max_workers:int, 56 | data_directory:Path, 57 | docker_repo_tag= 'latest' 58 | ) -> None: 59 | """ 60 | 61 | :param docker_container_url: Simulation container URL at container registry 62 | :param max_workers: number of parallel workers 63 | :param data_directory: path to the output directory on your host 64 | :param docker_repo_tag: container tag, Default: latest 65 | """ 66 | self._data_directory = data_directory 67 | self._max_workers = max_workers 68 | self._thread_pool_executor = concurrent.futures.ThreadPoolExecutor(max_workers=self._max_workers) 69 | self._container_prefix = 'DockerSim' 70 | self._docker_client = docker.from_env() 71 | self._authenticate_at_container_registry() 72 | with Halo(text='Pulling latest docker_sim image', spinner='dots'): 73 | self._docker_image = self._docker_client.images.pull( 74 | repository=docker_container_url, 75 | tag=docker_repo_tag 76 | ) 77 | self._io_lock = threading.Lock() 78 | self._monitoring_frequency = 300 79 | self._minimum_runtime = 300 80 | self._maximum_inactivity_time = 30 * 60 81 | self.job_list = [] 82 | 83 | 84 | def add_sim_job(self, job:SimJob)->None: 85 | """ 86 | Adds a simulation job into the queue 87 | :param job: simulation job to be added 88 | """ 89 | self.job_list.append(job) 90 | 91 | 92 | 93 | def start_computation(self): 94 | """ 95 | Starts computation of all jobs inside the queue. 96 | Jobs must be added before calling this function 97 | """ 98 | self.start_monitoring_thread() 99 | with self._thread_pool_executor as executor: 100 | futures = {executor.submit(self._process_sim_job, sim_job): sim_job 101 | for sim_job in self.job_list} 102 | for future in concurrent.futures.as_completed(futures): 103 | logger.info(f'Run {futures[future]} did finish') 104 | 105 | 106 | def _process_sim_job(self, sim_job: SimJob)->None: 107 | """ 108 | Triggers processing steps for a single job. 109 | 1. _init_simulation 110 | 2. _run_docker_container 111 | 3. _cleanup_sim_objects 112 | :param sim_job: SimJob to be processed 113 | :return: True if processing succeeded, False otherwise. 114 | """ 115 | sim_paths = self._init_simulation(sim_job=sim_job) 116 | if sim_paths is None: 117 | logger.error(f'Error during initialization for simulation {sim_job}') 118 | return False 119 | 120 | try: 121 | ( 122 | working_dir, 123 | *file_objects 124 | ) = sim_paths 125 | except: 126 | try: 127 | working_dir=sim_paths 128 | except: 129 | logger.error(f'Error during initialization for simulation {str(sim_job)}') 130 | return False 131 | 132 | self._run_docker_container(container_name=sim_job.sim_name, working_dir=working_dir, command=sim_job.command) 133 | self.cleanup_sim_objects(sim_job=sim_job, file_objects=file_objects) 134 | return True 135 | 136 | def _init_simulation(self, sim_job): 137 | """ 138 | Initialize simulation. May be overridden with custom function. 139 | - Create output folders 140 | - Copy file templates 141 | - ... 142 | :param sim_job: SimJob to be processed 143 | :return: Path to working directory on your host's filesystem for this SimJob 144 | """ 145 | # prepare your data for your scenario here 146 | output_folder_name = f'job_{sim_job.sim_name}' 147 | working_dir = self._data_directory.joinpath(output_folder_name) 148 | try: 149 | with self._io_lock: 150 | working_dir.mkdir(exist_ok=False, parents=True) 151 | # if you need additional files in your simulation e.g. config files, data, add them here example_monte_carlo_pi here 152 | return working_dir 153 | except Exception as e: 154 | logger.warning(e) 155 | return None 156 | pass 157 | 158 | 159 | def _run_docker_container(self, container_name, working_dir, command): 160 | """ 161 | Triggers the simulation run in a separate Docker container. 162 | :param container_name: the container's name 163 | :param working_dir: working directory on your host's file system 164 | :param command: container command (eg. name of script, cli arguments ...) Must match to your docker entry point. 165 | """ 166 | try: 167 | system_platform = platform.system() 168 | if system_platform == "Windows": 169 | self._docker_client.containers.run( 170 | image=self._docker_image, 171 | command=command, 172 | mounts=[Mount( 173 | target='/mnt/data', 174 | source=str(working_dir.resolve()), 175 | type='bind' 176 | )], 177 | #working_dir='/simulation', 178 | name=container_name, 179 | environment={ 180 | # If you need add your environment variables here 181 | }, 182 | log_config=LogConfig(type=LogConfig.types.JSON, config={ 183 | 'max-size': '500m', 184 | 'max-file': '3' 185 | }) 186 | ) 187 | else: 188 | user_id = os.getuid() 189 | self._docker_client.containers.run( 190 | image=self._docker_image, 191 | command=command, 192 | mounts=[Mount( 193 | target='/mnt/data', 194 | source=str(working_dir.resolve()), 195 | type='bind' 196 | )], 197 | working_dir='/mnt/data', 198 | name=container_name, 199 | environment={ 200 | # If you need add your environment variables here 201 | }, 202 | log_config=LogConfig(type=LogConfig.types.JSON, config={ 203 | 'max-size': '500m', 204 | 'max-file': '3' 205 | }), 206 | user=user_id 207 | ) 208 | except DockerException as e: 209 | logger.warning('Error in run {container_name}: {e}.') 210 | finally: 211 | try: 212 | self.write_container_logs_and_remove_it( 213 | container_name=container_name, 214 | working_dir=working_dir 215 | ) 216 | except NotFound: 217 | logger.warning(f'Can not save logs for {container_name}, because container does not exist') 218 | 219 | 220 | def start_monitoring_thread(self): 221 | """ 222 | Start a monitoring thread, which observes running docker containers. 223 | """ 224 | monitoring_thread = threading.Thread(target=self._monitor_containers, 225 | args=(self._container_prefix,), 226 | daemon=True, 227 | name='monitoring') 228 | monitoring_thread.start() 229 | 230 | def write_container_logs_and_remove_it(self, container_name, working_dir): 231 | """ 232 | Write container logs and remove the container from your docker server 233 | :param container_name: The container's name, which shall be removed 234 | :param working_dir: path, where logfiles shall be written to 235 | """ 236 | container = self._docker_client.containers.get(container_name) 237 | with open(working_dir.joinpath('log.txt'), 'w') as f: 238 | f.write(container.logs().decode('utf-8')) 239 | container.remove() 240 | 241 | def _authenticate_at_container_registry(self): 242 | """ 243 | Authenticate at container registry. NOTE: GitHub container registry is used in this example. 244 | If you want to user other container registries, like DockerHub or GitLab, feel free to adapt this method. 245 | Function either uses Environment variables for authentication or asks for login credentials in the command line. 246 | Note: GitHub container registry does not accept your personal password. You need to generate a personal access token (PAT) 247 | """ 248 | username = os.environ.get('GITHUB_USERNAME') 249 | password = os.environ.get('GITHUB_PAT') 250 | if username is None: 251 | username = input('Enter username for container registry: ') 252 | if password is None: 253 | password = getpass('Enter password for container registry: ') 254 | login_result = self._docker_client.login( 255 | registry='ghcr.io', 256 | username=username, 257 | password=password, 258 | reauth=True 259 | ) 260 | if login_result['Status'] != 'Login Succeeded': 261 | raise RuntimeError("Could not authenticate at GitHub container registry") 262 | else: 263 | logger.info("Successfully authenticated at GitHub container registry.") 264 | 265 | def _monitor_containers(self, container_prefix): 266 | """ 267 | Monitors all running docker containers. Inactive containers get killed after self._maximum_inactivity_time 268 | :param container_prefix: The containers prefix used for all containers in this simulation 269 | """ 270 | while True: 271 | containers = self._docker_client.containers.list() 272 | for container in containers: 273 | try: 274 | if container_prefix in container.name: 275 | container_start = dateutil.parser.isoparse(container.attrs['State']['StartedAt']) 276 | now = datetime.datetime.now(datetime.timezone.utc) 277 | uptime = (now - container_start).total_seconds() 278 | 279 | logs = container.logs(since=int(time.time() - self._maximum_inactivity_time)) 280 | 281 | if uptime > self._minimum_runtime and not logs: 282 | logger.warning(f'Container {container.name} ran for more than ' 283 | f'{self._minimum_runtime} seconds and showed no log activity for ' 284 | f'{self._maximum_inactivity_time} seconds.' 285 | f'It will be stopped.') 286 | container.stop() 287 | except APIError as e: 288 | logger.warning(f'Error during thread monitoring: {str(e)}') 289 | 290 | time.sleep(self._monitoring_frequency) 291 | 292 | 293 | @staticmethod 294 | def cleanup_sim_objects(sim_job:SimJob, file_objects): 295 | """ 296 | Clean up function for finished simulations. Removes all files given in file objects. 297 | :param sim_job: SimJob to be processed 298 | :param file_objects: files/directories to be removed after simulation 299 | """ 300 | file_object:Path 301 | for file_object in file_objects: 302 | if file_object.is_file(): 303 | try: 304 | file_object.unlink() 305 | except Exception as e: 306 | logger.warning(e) 307 | logger.warning(f'Error during cleanup for simulation {sim_job.sim_name}') 308 | elif file_object.is_dir(): 309 | try: 310 | shutil.rmtree(file_object) 311 | except Exception as e: 312 | logger.warning(e) 313 | logger.warning(f'Error during cleanup for simulation {sim_job.sim_name}') 314 | else: 315 | logger.warning(f"{file_object} is not a file or directory.") -------------------------------------------------------------------------------- /example_monte_carlo_pi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Example code to demonstrate DockerSimManager 3 | 4 | This example runs multiple monte carlo simulations to estimate pi 5 | """ 6 | 7 | from docker_sim_manager import DockerSimManager 8 | from pathlib import Path 9 | from docker_sim_manager import SimJob 10 | 11 | __author__ = "Michael Wittmann" 12 | __copyright__ = "Copyright 2020, Michael Wittmann" 13 | 14 | __license__ = "MIT" 15 | __version__ = "1.0.0" 16 | __maintainer__ = "Michael Wittmann" 17 | __email__ = "michael.wittmann@tum.de" 18 | __status__ = "Example" 19 | 20 | 21 | if __name__ == '__main__': 22 | 23 | # Choose output directory on your host's file system 24 | output_path = Path.home().joinpath('example_docker_simulation').joinpath('monte_carlo_pi') 25 | 26 | # Generate DockerSimManager object. Specify simulation container, number of parallel containers and output_path 27 | docker_manager = DockerSimManager('ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image', 28 | 1, 29 | output_path) 30 | 31 | # Add 20 simulation jobs to the job queue 32 | for i in range(1,20): 33 | docker_manager.add_sim_job(SimJob(f'IT{i}',None, command=f'-o /mnt/data -r {i} -i 100')) 34 | 35 | # Start computation 36 | docker_manager.start_computation() -------------------------------------------------------------------------------- /example_monte_carlo_pi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-buster 2 | 3 | LABEL maintainer="Michael Wittmann " 4 | 5 | ENV TZ=Europe/Berlin 6 | 7 | WORKDIR /usr/src/app 8 | 9 | COPY ./* ./ 10 | 11 | RUN pip install --upgrade pip 12 | RUN pip install poetry 13 | RUN poetry config virtualenvs.create false \ 14 | && poetry install --no-interaction --no-ansi 15 | 16 | ENTRYPOINT ["python", "./monte_carlo_pi.py"] 17 | -------------------------------------------------------------------------------- /example_monte_carlo_pi/README.md: -------------------------------------------------------------------------------- 1 | # Example | Monte Carlo Estimation of PI 2 | 3 | If you draw random points on a square, PI can be estimated by the ratio of points having a distance from the origin of less than 1 and the total-sample-count, multiplied by 4. 4 | 5 | This example project randomly samples points, estimats pi an generates a plot of your experiment. 6 | 7 | ## Basic Usage 8 | 9 | ### Install 10 | 1. This project uses poetry for dependency managment. First install poetry via pip 11 | ```shell script 12 | pip install poetry 13 | ``` 14 | 15 | 2. Run poetry install in this directory 16 | ```shell script 17 | poetry install 18 | ``` 19 | 20 | ### Run Script 21 | This script provides a command line interface to specify the parameters of your experiment . 22 | ```shell script 23 | python monte_carlo_pi.py - o -i -r ` 24 | ``` 25 | 26 | 27 | 28 | ## Docker Container 29 | The example is packed into a simple python docker container. The container is available in this repositories container registry. 30 | 31 | ### (Install Docker) 32 | https://docs.docker.com/get-docker/ 33 | 34 | ### Pull Docker container 35 | ```shell script 36 | docker pull ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest 37 | ``` 38 | 39 | ### Run Docker container 40 | ```shell script 41 | docker run --name MonteCarloPi -it --mount type=bind,src=DIR_ON_YOUR_HOST,dst=/mnt/output/ ghcr.io/michaelwittmann/docker_simulation_pipeline_example/monte-carlo-pi-image:latest -o /mnt/output -i 100 42 | ``` 43 | -------------------------------------------------------------------------------- /example_monte_carlo_pi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_monte_carlo_pi/__init__.py -------------------------------------------------------------------------------- /example_monte_carlo_pi/img/pi_estmimate_n1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_monte_carlo_pi/img/pi_estmimate_n1000.png -------------------------------------------------------------------------------- /example_monte_carlo_pi/img/pi_estmimate_n10000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_monte_carlo_pi/img/pi_estmimate_n10000.png -------------------------------------------------------------------------------- /example_monte_carlo_pi/img/pi_estmimate_n100000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_monte_carlo_pi/img/pi_estmimate_n100000.png -------------------------------------------------------------------------------- /example_monte_carlo_pi/img/pi_estmimate_n500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_monte_carlo_pi/img/pi_estmimate_n500.png -------------------------------------------------------------------------------- /example_monte_carlo_pi/monte_carlo_pi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Monte carlo estimation of pi 3 | 4 | Background: 5 | https://en.wikipedia.org/wiki/Monte_Carlo_method 6 | 7 | """ 8 | 9 | import random 10 | import math 11 | from pathlib import Path 12 | import matplotlib.pyplot as plt 13 | import numpy as np 14 | import time 15 | import sys 16 | import getopt 17 | 18 | __author__ = "Michael Wittmann and Maximilian Speicher" 19 | __copyright__ = "Copyright 2020, Michael Wittmann and Maximilian Speicher" 20 | 21 | __license__ = "MIT" 22 | __version__ = "1.0.0" 23 | __maintainer__ = "Michael Wittmann" 24 | __email__ = "michael.wittmann@tum.de" 25 | __status__ = "Example" 26 | 27 | 28 | class MonteCarloPi(): 29 | 30 | def __init__(self, iterations: int = 10000, random_seed: int = None, output_dir:Path = 'output') -> None: 31 | """ 32 | New instance of MonteCarloPI 33 | :param iterations: number of samples 34 | :param random_seed: random seed to be used for sampling 35 | :param output_dir: output directory for generated plots 36 | """ 37 | self.random_seed = random_seed 38 | self.iterations = iterations 39 | self.points_inside = [] 40 | self.points_outside = [] 41 | self.pi_estimate = None 42 | self.output_dir = output_dir 43 | 44 | if not output_dir.exists(): 45 | output_dir.mkdir(parents=True, exist_ok=True) 46 | 47 | 48 | def _f_circle(self, x, radius=1.0): 49 | """ 50 | Circle funcion $y=sqrt(r^2-x^2) 51 | :param x: x-value 52 | :param radius: circle radius 53 | :return: y-value 54 | """ 55 | y = np.sqrt(radius ** 2 - x ** 2) 56 | return y 57 | 58 | def estimate_pi(self) -> float: 59 | """ 60 | Estimate pi, with monte carlo method. 61 | https://en.wikipedia.org/wiki/Monte_Carlo_method 62 | :return: Estimate of pi 63 | """ 64 | self.points_inside = [] 65 | self.points_outside = [] 66 | random.seed(self.random_seed) 67 | 68 | for i in range(0, self.iterations): 69 | point = (random.random(), random.random()) 70 | if (math.sqrt(point[0] ** 2 + point[1] ** 2)) < 1.0: 71 | self.points_inside.append(point) 72 | else: 73 | self.points_outside.append(point) 74 | 75 | self.pi_estimate = (float(len(self.points_inside)) / float(self.iterations)) * 4.0 76 | return self.pi_estimate 77 | 78 | def plot(self, path=None): 79 | """ 80 | Plot img of monte-carlo estimation 81 | :param path: output directory, uses cwd if None 82 | """ 83 | x_circle = np.linspace(0, 1, 200) 84 | fig, ax = plt.subplots(1, 1, figsize=(5, 5), dpi=300) 85 | ax.scatter(*zip(*self.points_outside[:10000]), color='r', alpha=0.5, 86 | label=f'Points outside: $n={len(self.points_outside)}$') 87 | ax.scatter(*zip(*self.points_inside[:10000]), color='b', alpha=0.5, 88 | label=f'Points inside: $n={len(self.points_inside)}$') 89 | ax.plot(x_circle, self._f_circle(x_circle), color='black', linestyle='--', linewidth=2, label='circle') 90 | ax.set_title(f'Estimate of $\pi={self.pi_estimate:.10f}$\nIterations $n={self.iterations}$') 91 | ax.set_xlim(0, 1) 92 | ax.set_ylim(0, 1) 93 | ax.legend(loc='lower right') 94 | 95 | figure_path = self.output_dir.joinpath(f'pi_estmimate_n{self.iterations}_at{time.time_ns()}.png') 96 | plt.savefig(figure_path, dpi=300) 97 | 98 | 99 | def main(argv): 100 | output_folder:Path = Path('img') 101 | iterations: int = 100000 102 | random_seed:int = 1 103 | 104 | try: 105 | opts, args = getopt.getopt(argv, 'ho:i:r:',['output_dir=', '--iterations', '--random_seed']) 106 | except getopt.GetoptError: 107 | print('monte_carlo_pi.py - o -i -r ') 108 | sys.exit(2) 109 | 110 | 111 | for opt, arg in opts: 112 | if opt == '-h': 113 | print('monte_carlo_pi.py - o -i -r ') 114 | 115 | if opt in ('-o', '--output_dir'): 116 | output_folder = Path(arg) 117 | 118 | if opt in ('-i', '--iterations'): 119 | iterations = int(arg) 120 | 121 | if opt in ('-r', '--random_seed'): 122 | random_seed = int(arg) 123 | 124 | 125 | print(f'Starting Simulation: iteations={iterations}, random_seed={random_seed}') 126 | tic = time.time() 127 | simulation = MonteCarloPi(iterations=iterations, random_seed=random_seed, output_dir = output_folder) 128 | pi_hat = simulation.estimate_pi() 129 | print(f'Result: pi_hat = {pi_hat:.10f}') 130 | toc = time.time() 131 | print('Generating plot...') 132 | simulation.plot() 133 | print(f'Simulation finished! ({toc-tic:.5} ms) ') 134 | 135 | 136 | if __name__ == '__main__': 137 | main(sys.argv[1:]) 138 | -------------------------------------------------------------------------------- /example_monte_carlo_pi/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "atomicwrites" 5 | version = "1.4.0" 6 | description = "Atomic file writes." 7 | optional = false 8 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 9 | files = [ 10 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 11 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 12 | ] 13 | 14 | [[package]] 15 | name = "attrs" 16 | version = "20.3.0" 17 | description = "Classes Without Boilerplate" 18 | optional = false 19 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 20 | files = [ 21 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 22 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 23 | ] 24 | 25 | [package.extras] 26 | dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] 27 | docs = ["furo", "sphinx", "zope.interface"] 28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 29 | tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 30 | 31 | [[package]] 32 | name = "certifi" 33 | version = "2020.11.8" 34 | description = "Python package for providing Mozilla's CA Bundle." 35 | optional = false 36 | python-versions = "*" 37 | files = [ 38 | {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, 39 | {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, 40 | ] 41 | 42 | [[package]] 43 | name = "chardet" 44 | version = "3.0.4" 45 | description = "Universal encoding detector for Python 2 and 3" 46 | optional = false 47 | python-versions = "*" 48 | files = [ 49 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 50 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 51 | ] 52 | 53 | [[package]] 54 | name = "colorama" 55 | version = "0.4.4" 56 | description = "Cross-platform colored terminal text." 57 | optional = false 58 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 59 | files = [ 60 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 61 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 62 | ] 63 | 64 | [[package]] 65 | name = "cycler" 66 | version = "0.10.0" 67 | description = "Composable style cycles" 68 | optional = false 69 | python-versions = "*" 70 | files = [ 71 | {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, 72 | {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, 73 | ] 74 | 75 | [package.dependencies] 76 | six = "*" 77 | 78 | [[package]] 79 | name = "docker" 80 | version = "4.4.0" 81 | description = "A Python library for the Docker Engine API." 82 | optional = false 83 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 84 | files = [ 85 | {file = "docker-4.4.0-py2.py3-none-any.whl", hash = "sha256:317e95a48c32de8c1aac92a48066a5b73e218ed096e03758bcdd799a7130a1a1"}, 86 | {file = "docker-4.4.0.tar.gz", hash = "sha256:cffc771d4ea1389fc66bc95cb72d304aa41d1a1563482a9a000fba3a84ed5071"}, 87 | ] 88 | 89 | [package.dependencies] 90 | pywin32 = {version = "227", markers = "sys_platform == \"win32\""} 91 | requests = ">=2.14.2,<2.18.0 || >2.18.0" 92 | six = ">=1.4.0" 93 | websocket-client = ">=0.32.0" 94 | 95 | [package.extras] 96 | ssh = ["paramiko (>=2.4.2)"] 97 | tls = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"] 98 | 99 | [[package]] 100 | name = "flake8" 101 | version = "3.8.4" 102 | description = "the modular source code checker: pep8 pyflakes and co" 103 | optional = false 104 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 105 | files = [ 106 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 107 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 108 | ] 109 | 110 | [package.dependencies] 111 | mccabe = ">=0.6.0,<0.7.0" 112 | pycodestyle = ">=2.6.0a1,<2.7.0" 113 | pyflakes = ">=2.2.0,<2.3.0" 114 | 115 | [[package]] 116 | name = "halo" 117 | version = "0.0.31" 118 | description = "Beautiful terminal spinners in Python" 119 | optional = false 120 | python-versions = ">=3.4" 121 | files = [ 122 | {file = "halo-0.0.31-py2-none-any.whl", hash = "sha256:5350488fb7d2aa7c31a1344120cee67a872901ce8858f60da7946cef96c208ab"}, 123 | {file = "halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6"}, 124 | ] 125 | 126 | [package.dependencies] 127 | colorama = ">=0.3.9" 128 | log-symbols = ">=0.0.14" 129 | six = ">=1.12.0" 130 | spinners = ">=0.0.24" 131 | termcolor = ">=1.1.0" 132 | 133 | [package.extras] 134 | ipython = ["IPython (==5.7.0)", "ipywidgets (==7.1.0)"] 135 | 136 | [[package]] 137 | name = "idna" 138 | version = "2.10" 139 | description = "Internationalized Domain Names in Applications (IDNA)" 140 | optional = false 141 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 142 | files = [ 143 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 144 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 145 | ] 146 | 147 | [[package]] 148 | name = "iniconfig" 149 | version = "1.1.1" 150 | description = "iniconfig: brain-dead simple config-ini parsing" 151 | optional = false 152 | python-versions = "*" 153 | files = [ 154 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 155 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 156 | ] 157 | 158 | [[package]] 159 | name = "kiwisolver" 160 | version = "1.3.1" 161 | description = "A fast implementation of the Cassowary constraint solver" 162 | optional = false 163 | python-versions = ">=3.6" 164 | files = [ 165 | {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, 166 | {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, 167 | {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"}, 168 | {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"}, 169 | {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"}, 170 | {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"}, 171 | {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"}, 172 | {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"}, 173 | {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"}, 174 | {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"}, 175 | {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"}, 176 | {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"}, 177 | {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"}, 178 | {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"}, 179 | {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:24cc411232d14c8abafbd0dddb83e1a4f54d77770b53db72edcfe1d611b3bf11"}, 180 | {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"}, 181 | {file = "kiwisolver-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ef6eefcf3944e75508cdfa513c06cf80bafd7d179e14c1334ebdca9ebb8c2c66"}, 182 | {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"}, 183 | {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"}, 184 | {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"}, 185 | {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"}, 186 | {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"}, 187 | {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"}, 188 | {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6d9d8d9b31aa8c2d80a690693aebd8b5e2b7a45ab065bb78f1609995d2c79240"}, 189 | {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"}, 190 | {file = "kiwisolver-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:792e69140828babe9649de583e1a03a0f2ff39918a71782c76b3c683a67c6dfd"}, 191 | {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"}, 192 | {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"}, 193 | {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"}, 194 | {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"}, 195 | {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"}, 196 | {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"}, 197 | {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"}, 198 | {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"}, 199 | {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"}, 200 | {file = "kiwisolver-1.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6563ccd46b645e966b400bb8a95d3457ca6cf3bba1e908f9e0927901dfebeb1"}, 201 | {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"}, 202 | ] 203 | 204 | [[package]] 205 | name = "log-symbols" 206 | version = "0.0.14" 207 | description = "Colored symbols for various log levels for Python" 208 | optional = false 209 | python-versions = "*" 210 | files = [ 211 | {file = "log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca"}, 212 | {file = "log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556"}, 213 | ] 214 | 215 | [package.dependencies] 216 | colorama = ">=0.3.9" 217 | 218 | [[package]] 219 | name = "loguru" 220 | version = "0.5.3" 221 | description = "Python logging made (stupidly) simple" 222 | optional = false 223 | python-versions = ">=3.5" 224 | files = [ 225 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, 226 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, 227 | ] 228 | 229 | [package.dependencies] 230 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 231 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 232 | 233 | [package.extras] 234 | dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] 235 | 236 | [[package]] 237 | name = "matplotlib" 238 | version = "3.3.3" 239 | description = "Python plotting package" 240 | optional = false 241 | python-versions = ">=3.6" 242 | files = [ 243 | {file = "matplotlib-3.3.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b2a5e1f637a92bb6f3526cc54cc8af0401112e81ce5cba6368a1b7908f9e18bc"}, 244 | {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c586ac1d64432f92857c3cf4478cfb0ece1ae18b740593f8a39f2f0b27c7fda5"}, 245 | {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b03722c89a43a61d4d148acfc89ec5bb54cd0fd1539df25b10eb9c5fa6c393a"}, 246 | {file = "matplotlib-3.3.3-cp36-cp36m-win32.whl", hash = "sha256:2c2c5041608cb75c39cbd0ed05256f8a563e144234a524c59d091abbfa7a868f"}, 247 | {file = "matplotlib-3.3.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c092fc4673260b1446b8578015321081d5db73b94533fe4bf9b69f44e948d174"}, 248 | {file = "matplotlib-3.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27c9393fada62bd0ad7c730562a0fecbd3d5aaa8d9ed80ba7d3ebb8abc4f0453"}, 249 | {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b8ba2a1dbb4660cb469fe8e1febb5119506059e675180c51396e1723ff9b79d9"}, 250 | {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0caa687fce6174fef9b27d45f8cc57cbc572e04e98c81db8e628b12b563d59a2"}, 251 | {file = "matplotlib-3.3.3-cp37-cp37m-win32.whl", hash = "sha256:b7b09c61a91b742cb5460b72efd1fe26ef83c1c704f666e0af0df156b046aada"}, 252 | {file = "matplotlib-3.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6ffd2d80d76df2e5f9f0c0140b5af97e3b87dd29852dcdb103ec177d853ec06b"}, 253 | {file = "matplotlib-3.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5111d6d47a0f5b8f3e10af7a79d5e7eb7e73a22825391834734274c4f312a8a0"}, 254 | {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a4fe54eab2c7129add75154823e6543b10261f9b65b2abe692d68743a4999f8c"}, 255 | {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:83e6c895d93fdf93eeff1a21ee96778ba65ef258e5d284160f7c628fee40c38f"}, 256 | {file = "matplotlib-3.3.3-cp38-cp38-win32.whl", hash = "sha256:b26c472847911f5a7eb49e1c888c31c77c4ddf8023c1545e0e8e0367ba74fb15"}, 257 | {file = "matplotlib-3.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:09225edca87a79815822eb7d3be63a83ebd4d9d98d5aa3a15a94f4eee2435954"}, 258 | {file = "matplotlib-3.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb6b6700ea454bb88333d98601e74928e06f9669c1ea231b4c4c666c1d7701b4"}, 259 | {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2d31aff0c8184b05006ad756b9a4dc2a0805e94d28f3abc3187e881b6673b302"}, 260 | {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d082f77b4ed876ae94a9373f0db96bf8768a7cca6c58fc3038f94e30ffde1880"}, 261 | {file = "matplotlib-3.3.3-cp39-cp39-win32.whl", hash = "sha256:e71cdd402047e657c1662073e9361106c6981e9621ab8c249388dfc3ec1de07b"}, 262 | {file = "matplotlib-3.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:756ee498b9ba35460e4cbbd73f09018e906daa8537fff61da5b5bf8d5e9de5c7"}, 263 | {file = "matplotlib-3.3.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ad44f2c74c50567c694ee91c6fa16d67e7c8af6f22c656b80469ad927688457"}, 264 | {file = "matplotlib-3.3.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:3a4c3e9be63adf8e9b305aa58fb3ec40ecc61fd0f8fd3328ce55bc30e7a2aeb0"}, 265 | {file = "matplotlib-3.3.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:746897fbd72bd462b888c74ed35d812ca76006b04f717cd44698cdfc99aca70d"}, 266 | {file = "matplotlib-3.3.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:5ed3d3342698c2b1f3651f8ea6c099b0f196d16ee00e33dc3a6fee8cb01d530a"}, 267 | {file = "matplotlib-3.3.3.tar.gz", hash = "sha256:b1b60c6476c4cfe9e5cf8ab0d3127476fd3d5f05de0f343a452badaad0e4bdec"}, 268 | ] 269 | 270 | [package.dependencies] 271 | cycler = ">=0.10" 272 | kiwisolver = ">=1.0.1" 273 | numpy = ">=1.15" 274 | pillow = ">=6.2.0" 275 | pyparsing = ">=2.0.3,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6" 276 | python-dateutil = ">=2.1" 277 | 278 | [[package]] 279 | name = "mccabe" 280 | version = "0.6.1" 281 | description = "McCabe checker, plugin for flake8" 282 | optional = false 283 | python-versions = "*" 284 | files = [ 285 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 286 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 287 | ] 288 | 289 | [[package]] 290 | name = "numpy" 291 | version = "1.22.0" 292 | description = "NumPy is the fundamental package for array computing with Python." 293 | optional = false 294 | python-versions = ">=3.8" 295 | files = [ 296 | {file = "numpy-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d22662b4b10112c545c91a0741f2436f8ca979ab3d69d03d19322aa970f9695"}, 297 | {file = "numpy-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a1f3816ea82eed4178102c56281782690ab5993251fdfd75039aad4d20385f"}, 298 | {file = "numpy-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5dc65644f75a4c2970f21394ad8bea1a844104f0fe01f278631be1c7eae27226"}, 299 | {file = "numpy-1.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c16cec1c8cf2728f1d539bd55aaa9d6bb48a7de2f41eb944697293ef65a559"}, 300 | {file = "numpy-1.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97e82c39d9856fe7d4f9b86d8a1e66eff99cf3a8b7ba48202f659703d27c46f"}, 301 | {file = "numpy-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:e41e8951749c4b5c9a2dc5fdbc1a4eec6ab2a140fdae9b460b0f557eed870f4d"}, 302 | {file = "numpy-1.22.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bece0a4a49e60e472a6d1f70ac6cdea00f9ab80ff01132f96bd970cdd8a9e5a9"}, 303 | {file = "numpy-1.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:818b9be7900e8dc23e013a92779135623476f44a0de58b40c32a15368c01d471"}, 304 | {file = "numpy-1.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47ee7a839f5885bc0c63a74aabb91f6f40d7d7b639253768c4199b37aede7982"}, 305 | {file = "numpy-1.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a024181d7aef0004d76fb3bce2a4c9f2e67a609a9e2a6ff2571d30e9976aa383"}, 306 | {file = "numpy-1.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f71d57cc8645f14816ae249407d309be250ad8de93ef61d9709b45a0ddf4050c"}, 307 | {file = "numpy-1.22.0-cp38-cp38-win32.whl", hash = "sha256:283d9de87c0133ef98f93dfc09fad3fb382f2a15580de75c02b5bb36a5a159a5"}, 308 | {file = "numpy-1.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:2762331de395739c91f1abb88041f94a080cb1143aeec791b3b223976228af3f"}, 309 | {file = "numpy-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:76ba7c40e80f9dc815c5e896330700fd6e20814e69da9c1267d65a4d051080f1"}, 310 | {file = "numpy-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0cfe07133fd00b27edee5e6385e333e9eeb010607e8a46e1cd673f05f8596595"}, 311 | {file = "numpy-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6ed0d073a9c54ac40c41a9c2d53fcc3d4d4ed607670b9e7b0de1ba13b4cbfe6f"}, 312 | {file = "numpy-1.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41388e32e40b41dd56eb37fcaa7488b2b47b0adf77c66154d6b89622c110dfe9"}, 313 | {file = "numpy-1.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b55b953a1bdb465f4dc181758570d321db4ac23005f90ffd2b434cc6609a63dd"}, 314 | {file = "numpy-1.22.0-cp39-cp39-win32.whl", hash = "sha256:5a311ee4d983c487a0ab546708edbdd759393a3dc9cd30305170149fedd23c88"}, 315 | {file = "numpy-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:a97a954a8c2f046d3817c2bce16e3c7e9a9c2afffaf0400f5c16df5172a67c9c"}, 316 | {file = "numpy-1.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb02929b0d6bfab4c48a79bd805bd7419114606947ec8284476167415171f55b"}, 317 | {file = "numpy-1.22.0.zip", hash = "sha256:a955e4128ac36797aaffd49ab44ec74a71c11d6938df83b1285492d277db5397"}, 318 | ] 319 | 320 | [[package]] 321 | name = "packaging" 322 | version = "20.4" 323 | description = "Core utilities for Python packages" 324 | optional = false 325 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 326 | files = [ 327 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 328 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 329 | ] 330 | 331 | [package.dependencies] 332 | pyparsing = ">=2.0.2" 333 | six = "*" 334 | 335 | [[package]] 336 | name = "pillow" 337 | version = "9.3.0" 338 | description = "Python Imaging Library (Fork)" 339 | optional = false 340 | python-versions = ">=3.7" 341 | files = [ 342 | {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, 343 | {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, 344 | {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, 345 | {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, 346 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, 347 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, 348 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, 349 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, 350 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, 351 | {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, 352 | {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, 353 | {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, 354 | {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, 355 | {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, 356 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, 357 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, 358 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, 359 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, 360 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, 361 | {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, 362 | {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, 363 | {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, 364 | {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, 365 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, 366 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, 367 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, 368 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, 369 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, 370 | {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, 371 | {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, 372 | {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, 373 | {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, 374 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, 375 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, 376 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, 377 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, 378 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, 379 | {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, 380 | {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, 381 | {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, 382 | {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, 383 | {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, 384 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, 385 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, 386 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, 387 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, 388 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, 389 | {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, 390 | {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, 391 | {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, 392 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, 393 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, 394 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, 395 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, 396 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, 397 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, 398 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, 399 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, 400 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, 401 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, 402 | {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, 403 | ] 404 | 405 | [package.extras] 406 | docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] 407 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] 408 | 409 | [[package]] 410 | name = "pluggy" 411 | version = "0.13.1" 412 | description = "plugin and hook calling mechanisms for python" 413 | optional = false 414 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 415 | files = [ 416 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 417 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 418 | ] 419 | 420 | [package.extras] 421 | dev = ["pre-commit", "tox"] 422 | 423 | [[package]] 424 | name = "py" 425 | version = "1.10.0" 426 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 427 | optional = false 428 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 429 | files = [ 430 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 431 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 432 | ] 433 | 434 | [[package]] 435 | name = "pycodestyle" 436 | version = "2.6.0" 437 | description = "Python style guide checker" 438 | optional = false 439 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 440 | files = [ 441 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 442 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 443 | ] 444 | 445 | [[package]] 446 | name = "pyflakes" 447 | version = "2.2.0" 448 | description = "passive checker of Python programs" 449 | optional = false 450 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 451 | files = [ 452 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 453 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 454 | ] 455 | 456 | [[package]] 457 | name = "pyparsing" 458 | version = "2.4.7" 459 | description = "Python parsing module" 460 | optional = false 461 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 462 | files = [ 463 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 464 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 465 | ] 466 | 467 | [[package]] 468 | name = "pytest" 469 | version = "6.1.2" 470 | description = "pytest: simple powerful testing with Python" 471 | optional = false 472 | python-versions = ">=3.5" 473 | files = [ 474 | {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, 475 | {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, 476 | ] 477 | 478 | [package.dependencies] 479 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 480 | attrs = ">=17.4.0" 481 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 482 | iniconfig = "*" 483 | packaging = "*" 484 | pluggy = ">=0.12,<1.0" 485 | py = ">=1.8.2" 486 | toml = "*" 487 | 488 | [package.extras] 489 | checkqa-mypy = ["mypy (==0.780)"] 490 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 491 | 492 | [[package]] 493 | name = "python-dateutil" 494 | version = "2.8.1" 495 | description = "Extensions to the standard Python datetime module" 496 | optional = false 497 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 498 | files = [ 499 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 500 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 501 | ] 502 | 503 | [package.dependencies] 504 | six = ">=1.5" 505 | 506 | [[package]] 507 | name = "pywin32" 508 | version = "227" 509 | description = "Python for Window Extensions" 510 | optional = false 511 | python-versions = "*" 512 | files = [ 513 | {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, 514 | {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, 515 | {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, 516 | {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, 517 | {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, 518 | {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, 519 | {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, 520 | {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, 521 | {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, 522 | {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, 523 | {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, 524 | {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, 525 | ] 526 | 527 | [[package]] 528 | name = "requests" 529 | version = "2.25.0" 530 | description = "Python HTTP for Humans." 531 | optional = false 532 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 533 | files = [ 534 | {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, 535 | {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, 536 | ] 537 | 538 | [package.dependencies] 539 | certifi = ">=2017.4.17" 540 | chardet = ">=3.0.2,<4" 541 | idna = ">=2.5,<3" 542 | urllib3 = ">=1.21.1,<1.27" 543 | 544 | [package.extras] 545 | security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] 546 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 547 | 548 | [[package]] 549 | name = "six" 550 | version = "1.15.0" 551 | description = "Python 2 and 3 compatibility utilities" 552 | optional = false 553 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 554 | files = [ 555 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 556 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 557 | ] 558 | 559 | [[package]] 560 | name = "spinners" 561 | version = "0.0.24" 562 | description = "Spinners for terminals" 563 | optional = false 564 | python-versions = "*" 565 | files = [ 566 | {file = "spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98"}, 567 | {file = "spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f"}, 568 | ] 569 | 570 | [[package]] 571 | name = "termcolor" 572 | version = "1.1.0" 573 | description = "ANSII Color formatting for output in terminal." 574 | optional = false 575 | python-versions = "*" 576 | files = [ 577 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 578 | ] 579 | 580 | [[package]] 581 | name = "toml" 582 | version = "0.10.2" 583 | description = "Python Library for Tom's Obvious, Minimal Language" 584 | optional = false 585 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 586 | files = [ 587 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 588 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 589 | ] 590 | 591 | [[package]] 592 | name = "urllib3" 593 | version = "1.26.5" 594 | description = "HTTP library with thread-safe connection pooling, file post, and more." 595 | optional = false 596 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 597 | files = [ 598 | {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, 599 | {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, 600 | ] 601 | 602 | [package.extras] 603 | brotli = ["brotlipy (>=0.6.0)"] 604 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] 605 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 606 | 607 | [[package]] 608 | name = "websocket-client" 609 | version = "0.57.0" 610 | description = "WebSocket client for Python. hybi13 is supported." 611 | optional = false 612 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 613 | files = [ 614 | {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, 615 | {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, 616 | ] 617 | 618 | [package.dependencies] 619 | six = "*" 620 | 621 | [[package]] 622 | name = "win32-setctime" 623 | version = "1.0.3" 624 | description = "A small Python utility to set file creation time on Windows" 625 | optional = false 626 | python-versions = ">=3.5" 627 | files = [ 628 | {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, 629 | {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, 630 | ] 631 | 632 | [package.extras] 633 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 634 | 635 | [metadata] 636 | lock-version = "2.0" 637 | python-versions = "^3.9" 638 | content-hash = "707059800baec9ed4eb116fbd8f6342c66ccdcc5d311913ab50731282e27e54b" 639 | -------------------------------------------------------------------------------- /example_monte_carlo_pi/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "docker_monte_carlo_sim" 3 | version = "0.1.0" 4 | description = "A tutorial on how to scale simulations via docker containers" 5 | authors = ["Michael Wittmann "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | matplotlib = "^3.3.3" 10 | halo = "^0.0.31" 11 | loguru = "^0.5.3" 12 | docker = "^4.3.1" 13 | 14 | [tool.poetry.dev-dependencies] 15 | flake8 = "^3.8.4" 16 | pytest = "^6.1.2" 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /example_monte_carlo_pi/test_monte_carlo_pi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Example Test cases for monte_carlo_pi 3 | 4 | They are mainly used to show the integration of test-cases in a gitHub CI/CD pipeline. 5 | """ 6 | 7 | from pathlib import Path 8 | from unittest import TestCase 9 | import math 10 | from example_monte_carlo_pi.monte_carlo_pi import MonteCarloPi 11 | 12 | 13 | __author__ = "Michael Wittmann " 14 | __copyright__ = "Copyright 2020, Michael Wittmann" 15 | 16 | __license__ = "MIT" 17 | __version__ = "1.0.0" 18 | __maintainer__ = "Michael Wittmann" 19 | __email__ = "michael.wittmann@tum.de" 20 | __status__ = "Example" 21 | 22 | class TestMonteCarloPi(TestCase): 23 | 24 | 25 | def test_estimate_pi(self): 26 | simulation = MonteCarloPi(iterations=100000, random_seed=12345, output_dir=Path('ouput')) 27 | pi_hat = simulation.estimate_pi() 28 | print(abs(pi_hat/math.pi -1) ) 29 | self.assertTrue(abs(pi_hat/math.pi -1) < 0.001) 30 | 31 | 32 | def test_plot(self): 33 | simulation = MonteCarloPi(iterations=100000, random_seed=12345, output_dir=Path('ouput')) 34 | simulation.estimate_pi() 35 | simulation.plot() 36 | 37 | -------------------------------------------------------------------------------- /example_random_art.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Example code to demonstrate DockerSimManager 3 | 4 | This example generates random art in different scales and sizes 5 | """ 6 | 7 | from docker_sim_manager import DockerSimManager 8 | from pathlib import Path 9 | from docker_sim_manager import SimJob 10 | 11 | __author__ = "Michael Wittmann" 12 | __copyright__ = "Copyright 2020, Michael Wittmann" 13 | 14 | __license__ = "MIT" 15 | __version__ = "1.0.0" 16 | __maintainer__ = "Michael Wittmann" 17 | __email__ = "michael.wittmann@tum.de" 18 | __status__ = "Example" 19 | 20 | 21 | if __name__ == '__main__': 22 | 23 | # Choose output directory on your host's file system 24 | output_folder = Path.home().joinpath('example_docker_simulation').joinpath('random_art') 25 | 26 | # Generate DockerSimManager object. Specify simulation container, number of parallel containers and output_path 27 | docker_manager = DockerSimManager('ghcr.io/michaelwittmann/docker_simulation_pipeline_example/random-art-image', 28 | 10, 29 | output_folder) 30 | 31 | # Add 20 simulation jobs to the job queue 32 | for i in range(1,10): 33 | docker_manager.add_sim_job(SimJob(f'randomArt_size{i}x{i}',None, command=f'-g {i} -i 30 -s 5000 -o /mnt/data -n 50')) 34 | 35 | for i in range(1,10): 36 | docker_manager.add_sim_job(SimJob(f'random_Art_invaders_{i}',None, command=f'-g 10 -i {i} -s 5000 -o /mnt/data -n 50')) 37 | 38 | # Start computation 39 | docker_manager.start_computation() -------------------------------------------------------------------------------- /example_random_art/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-buster 2 | 3 | LABEL maintainer="Michael Wittmann " 4 | 5 | ENV TZ=Europe/Berlin 6 | 7 | WORKDIR /usr/src/app 8 | 9 | COPY ./* ./ 10 | 11 | RUN pip install --upgrade pip 12 | RUN pip install poetry 13 | RUN poetry config virtualenvs.create false \ 14 | && poetry install --no-interaction --no-ansi 15 | 16 | ENTRYPOINT ["python", "./sprithering.py"] -------------------------------------------------------------------------------- /example_random_art/README.md: -------------------------------------------------------------------------------- 1 | # Example | Generate Random Art 2 | 3 | This example is inspired by https://www.freecodecamp.org/news/how-to-create-generative-art-in-less-than-100-lines-of-code-d37f379859f/ 4 | Main elements of the code where taken form this blog post. I did some minor modifications to meet python3 standards and added a cli. 5 | 6 | 7 | # Random Art 8 | 9 | Fore more infromations please read, like and share the article of Eric Davidson at [www.freecodecamp.org](https://www.freecodecamp.org/news/how-to-create-generative-art-in-less-than-100-lines-of-code-d37f379859f/) . 10 | 11 | 12 | ## Basic Usage 13 | 14 | ### Install 15 | 1. This project uses poetry for dependency management. First install poetry via pip 16 | ```shell script 17 | pip install poetry 18 | ``` 19 | 20 | 2. Run poetry install in this directory 21 | ```shell script 22 | poetry install 23 | ``` 24 | 25 | ### Run Script 26 | This script provides a command line interface to specify the parameters of your experiment . 27 | ```shell script 28 | python sprithering.py -o -g -i -s -n 29 | ``` 30 | 31 | 32 | 33 | ## Docker Container 34 | The example is packed into a simple python docker container. The container is available in this repositories container registry. 35 | 36 | ### (Install Docker) 37 | https://docs.docker.com/get-docker/ 38 | 39 | ### Pull Docker container 40 | ```shell script 41 | docker pull ghcr.io/michaelwittmann/docker_simulation_pipeline_example/random-art-image:latest 42 | ``` 43 | 44 | ### Run Docker container 45 | ```shell script 46 | docker run --name RandomArt -it --mount type=bind,src=C:\Users\gu92jih\docker_sim,dst=/mnt/output/ ghcr.io/michaelwittmann/docker_simulation_pipeline_example/random-art-image:latest -o /mnt/output -g 15 -i 30 -s 5000 -n 2 47 | ``` 48 | -------------------------------------------------------------------------------- /example_random_art/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_random_art/__init__.py -------------------------------------------------------------------------------- /example_random_art/img/15x15-30-5000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_random_art/img/15x15-30-5000.jpg -------------------------------------------------------------------------------- /example_random_art/img/15x15-5-5000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_random_art/img/15x15-5-5000.jpg -------------------------------------------------------------------------------- /example_random_art/img/5x5-10-5000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/example_random_art/img/5x5-10-5000.jpg -------------------------------------------------------------------------------- /example_random_art/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "asgiref" 5 | version = "3.3.1" 6 | description = "ASGI specs, helper code, and adapters" 7 | optional = false 8 | python-versions = ">=3.5" 9 | files = [ 10 | {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, 11 | {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, 12 | ] 13 | 14 | [package.extras] 15 | tests = ["pytest", "pytest-asyncio"] 16 | 17 | [[package]] 18 | name = "atomicwrites" 19 | version = "1.4.0" 20 | description = "Atomic file writes." 21 | optional = false 22 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 23 | files = [ 24 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 25 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 26 | ] 27 | 28 | [[package]] 29 | name = "attrs" 30 | version = "20.3.0" 31 | description = "Classes Without Boilerplate" 32 | optional = false 33 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 34 | files = [ 35 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 36 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 37 | ] 38 | 39 | [package.extras] 40 | dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] 41 | docs = ["furo", "sphinx", "zope.interface"] 42 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 43 | tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 44 | 45 | [[package]] 46 | name = "colorama" 47 | version = "0.4.4" 48 | description = "Cross-platform colored terminal text." 49 | optional = false 50 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 51 | files = [ 52 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 53 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 54 | ] 55 | 56 | [[package]] 57 | name = "django" 58 | version = "3.1.14" 59 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 60 | optional = false 61 | python-versions = ">=3.6" 62 | files = [ 63 | {file = "Django-3.1.14-py3-none-any.whl", hash = "sha256:0fabc786489af16ad87a8c170ba9d42bfd23f7b699bd5ef05675864e8d012859"}, 64 | {file = "Django-3.1.14.tar.gz", hash = "sha256:72a4a5a136a214c39cf016ccdd6b69e2aa08c7479c66d93f3a9b5e4bb9d8a347"}, 65 | ] 66 | 67 | [package.dependencies] 68 | asgiref = ">=3.2.10,<4" 69 | pytz = "*" 70 | sqlparse = ">=0.2.2" 71 | 72 | [package.extras] 73 | argon2 = ["argon2-cffi (>=16.1.0)"] 74 | bcrypt = ["bcrypt"] 75 | 76 | [[package]] 77 | name = "flake8" 78 | version = "3.8.4" 79 | description = "the modular source code checker: pep8 pyflakes and co" 80 | optional = false 81 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 82 | files = [ 83 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 84 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 85 | ] 86 | 87 | [package.dependencies] 88 | mccabe = ">=0.6.0,<0.7.0" 89 | pycodestyle = ">=2.6.0a1,<2.7.0" 90 | pyflakes = ">=2.2.0,<2.3.0" 91 | 92 | [[package]] 93 | name = "image" 94 | version = "1.5.33" 95 | description = "Django application that provides cropping, resizing, thumbnailing, overlays and masking for images and videos with the ability to set the center of attention," 96 | optional = false 97 | python-versions = "*" 98 | files = [ 99 | {file = "image-1.5.33.tar.gz", hash = "sha256:baa2e09178277daa50f22fd6d1d51ec78f19c12688921cb9ab5808743f097126"}, 100 | ] 101 | 102 | [package.dependencies] 103 | django = "*" 104 | pillow = "*" 105 | six = "*" 106 | 107 | [[package]] 108 | name = "iniconfig" 109 | version = "1.1.1" 110 | description = "iniconfig: brain-dead simple config-ini parsing" 111 | optional = false 112 | python-versions = "*" 113 | files = [ 114 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 115 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 116 | ] 117 | 118 | [[package]] 119 | name = "mccabe" 120 | version = "0.6.1" 121 | description = "McCabe checker, plugin for flake8" 122 | optional = false 123 | python-versions = "*" 124 | files = [ 125 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 126 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 127 | ] 128 | 129 | [[package]] 130 | name = "packaging" 131 | version = "20.4" 132 | description = "Core utilities for Python packages" 133 | optional = false 134 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 135 | files = [ 136 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 137 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 138 | ] 139 | 140 | [package.dependencies] 141 | pyparsing = ">=2.0.2" 142 | six = "*" 143 | 144 | [[package]] 145 | name = "pillow" 146 | version = "9.3.0" 147 | description = "Python Imaging Library (Fork)" 148 | optional = false 149 | python-versions = ">=3.7" 150 | files = [ 151 | {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, 152 | {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, 153 | {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, 154 | {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, 155 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, 156 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, 157 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, 158 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, 159 | {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, 160 | {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, 161 | {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, 162 | {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, 163 | {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, 164 | {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, 165 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, 166 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, 167 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, 168 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, 169 | {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, 170 | {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, 171 | {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, 172 | {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, 173 | {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, 174 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, 175 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, 176 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, 177 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, 178 | {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, 179 | {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, 180 | {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, 181 | {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, 182 | {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, 183 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, 184 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, 185 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, 186 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, 187 | {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, 188 | {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, 189 | {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, 190 | {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, 191 | {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, 192 | {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, 193 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, 194 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, 195 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, 196 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, 197 | {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, 198 | {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, 199 | {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, 200 | {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, 201 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, 202 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, 203 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, 204 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, 205 | {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, 206 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, 207 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, 208 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, 209 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, 210 | {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, 211 | {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, 212 | ] 213 | 214 | [package.extras] 215 | docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] 216 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] 217 | 218 | [[package]] 219 | name = "pluggy" 220 | version = "0.13.1" 221 | description = "plugin and hook calling mechanisms for python" 222 | optional = false 223 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 224 | files = [ 225 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 226 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 227 | ] 228 | 229 | [package.extras] 230 | dev = ["pre-commit", "tox"] 231 | 232 | [[package]] 233 | name = "py" 234 | version = "1.10.0" 235 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 236 | optional = false 237 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 238 | files = [ 239 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 240 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 241 | ] 242 | 243 | [[package]] 244 | name = "pycodestyle" 245 | version = "2.6.0" 246 | description = "Python style guide checker" 247 | optional = false 248 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 249 | files = [ 250 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 251 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 252 | ] 253 | 254 | [[package]] 255 | name = "pyflakes" 256 | version = "2.2.0" 257 | description = "passive checker of Python programs" 258 | optional = false 259 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 260 | files = [ 261 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 262 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 263 | ] 264 | 265 | [[package]] 266 | name = "pyparsing" 267 | version = "2.4.7" 268 | description = "Python parsing module" 269 | optional = false 270 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 271 | files = [ 272 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 273 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 274 | ] 275 | 276 | [[package]] 277 | name = "pytest" 278 | version = "6.1.2" 279 | description = "pytest: simple powerful testing with Python" 280 | optional = false 281 | python-versions = ">=3.5" 282 | files = [ 283 | {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, 284 | {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, 285 | ] 286 | 287 | [package.dependencies] 288 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 289 | attrs = ">=17.4.0" 290 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 291 | iniconfig = "*" 292 | packaging = "*" 293 | pluggy = ">=0.12,<1.0" 294 | py = ">=1.8.2" 295 | toml = "*" 296 | 297 | [package.extras] 298 | checkqa-mypy = ["mypy (==0.780)"] 299 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 300 | 301 | [[package]] 302 | name = "pytz" 303 | version = "2020.4" 304 | description = "World timezone definitions, modern and historical" 305 | optional = false 306 | python-versions = "*" 307 | files = [ 308 | {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, 309 | {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, 310 | ] 311 | 312 | [[package]] 313 | name = "six" 314 | version = "1.15.0" 315 | description = "Python 2 and 3 compatibility utilities" 316 | optional = false 317 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 318 | files = [ 319 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 320 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 321 | ] 322 | 323 | [[package]] 324 | name = "sqlparse" 325 | version = "0.4.2" 326 | description = "A non-validating SQL parser." 327 | optional = false 328 | python-versions = ">=3.5" 329 | files = [ 330 | {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, 331 | {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, 332 | ] 333 | 334 | [[package]] 335 | name = "toml" 336 | version = "0.10.2" 337 | description = "Python Library for Tom's Obvious, Minimal Language" 338 | optional = false 339 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 340 | files = [ 341 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 342 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 343 | ] 344 | 345 | [metadata] 346 | lock-version = "2.0" 347 | python-versions = "^3.9" 348 | content-hash = "f819813ac4afef9d18dddd36e96b443926691c256d4583083ec1103305d0ceae" 349 | -------------------------------------------------------------------------------- /example_random_art/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "generative_art" 3 | version = "1.0.0" 4 | description = "Random Art Generator" 5 | authors = ["Eric Davidson", "Michael Wittmann =1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /example_random_art/sprithering.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This script generates random colored pixel graphics. 3 | 4 | A detailed blog post on this script can be found under 5 | https://www.freecodecamp.org/news/how-to-create-generative-art-in-less-than-100-lines-of-code-d37f379859f/ 6 | """ 7 | 8 | import getopt 9 | import random 10 | import sys 11 | import time 12 | from pathlib import Path 13 | 14 | from PIL import Image, ImageDraw 15 | 16 | __author__ = "Michael Wittmann and Eric Davidson" 17 | 18 | __version__ = "1.0.0" 19 | __maintainer__ = "Michael Wittmann" 20 | __email__ = "michael.wittmann@tum.de" 21 | __status__ = "Example" 22 | 23 | 24 | def r(): 25 | return random.randint(0, 255) 26 | 27 | 28 | def rc(): 29 | return r(), r(), r() 30 | 31 | 32 | listSym = [] 33 | 34 | 35 | def create_square(border, draw, randColor, element, size): 36 | if element == int(size / 2): 37 | draw.rectangle(border, randColor) 38 | elif len(listSym) == element + 1: 39 | draw.rectangle(border, listSym.pop()) 40 | else: 41 | listSym.append(randColor) 42 | draw.rectangle(border, randColor) 43 | 44 | 45 | def create_invader(border, draw, size): 46 | x0, y0, x1, y1 = border 47 | squareSize = (x1 - x0) / size 48 | randColors = [rc(), rc(), rc(), (0, 0, 0), (0, 0, 0), (0, 0, 0)] 49 | i = 1 50 | for y in range(0, size): 51 | i *= -1 52 | element = 0 53 | for x in range(0, size): 54 | topLeftX = x * squareSize + x0 55 | topLeftY = y * squareSize + y0 56 | botRightX = topLeftX + squareSize 57 | botRightY = topLeftY + squareSize 58 | create_square((topLeftX, topLeftY, botRightX, botRightY), draw, random.choice(randColors), element, size) 59 | if element == int(size / 2) or element == 0: 60 | i *= -1 61 | element += i 62 | 63 | 64 | def main(size:int, invaders:int, imgSize:int, output_path:Path, samples:int): 65 | if not output_path.exists(): 66 | output_path.mkdir(parents=True, exist_ok=True) 67 | origDimension = imgSize 68 | for n in range(0, samples): 69 | origImage = Image.new('RGB', (origDimension, origDimension)) 70 | draw = ImageDraw.Draw(origImage) 71 | invaderSize = origDimension / invaders 72 | padding = invaderSize / size 73 | for x in range(0, invaders): 74 | for y in range(0, invaders): 75 | topLeftX = x * invaderSize + padding / 2 76 | topLeftY = y * invaderSize + padding / 2 77 | botRightX = topLeftX + invaderSize - padding 78 | botRightY = topLeftY + invaderSize - padding 79 | create_invader((topLeftX, topLeftY, botRightX, botRightY), draw, size) 80 | 81 | file_name = f'{size}x{size}-{invaders}-{imgSize}-{time.time_ns()}.jpg' 82 | origImage.save(output_path.joinpath(file_name)) 83 | 84 | 85 | if __name__ == "__main__": 86 | output_folder: Path = Path('art') 87 | size: int = 15 88 | invaders: int = 30 89 | imgSize: int = 3000 90 | samples:int = 20 91 | 92 | try: 93 | opts, args = getopt.getopt(sys.argv[1:], 'ho:g:i:s:n:', ['--output_dir=','--grid_size=', '--invaders=', '--img_size=', '--samples=']) 94 | except getopt.GetoptError: 95 | print('sprithering.py -o -g -i -s -n ') 96 | sys.exit(2) 97 | 98 | for opt, arg in opts: 99 | if opt == '-h': 100 | print('sprithering.py -o -g -i -s -n ') 101 | 102 | if opt in ('-o', '--output_dir='): 103 | output_folder = Path(arg) 104 | 105 | if opt in ('-i', '--invaders='): 106 | invaders = int(arg) 107 | 108 | if opt in ('-s', '--imgsize='): 109 | imgSize = int(arg) 110 | 111 | if opt in ('-g', '--grid_size='): 112 | size = int(arg) 113 | 114 | if opt in ('-n', '--samples='): 115 | samples = int(arg) 116 | 117 | 118 | main(size=size, invaders=invaders, samples=samples, imgSize=imgSize, output_path=output_folder) 119 | -------------------------------------------------------------------------------- /img/actions_menu_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/actions_menu_bar.png -------------------------------------------------------------------------------- /img/concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/concept.png -------------------------------------------------------------------------------- /img/packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/packages.png -------------------------------------------------------------------------------- /img/packages_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/packages_details.png -------------------------------------------------------------------------------- /img/workflow_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/workflow_details.png -------------------------------------------------------------------------------- /img/workflows-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelwittmann/docker_simulation_pipeline_example/b372e69bcbbe0e83b8c20787389a19120383d104/img/workflows-overview.png -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "certifi" 5 | version = "2023.7.22" 6 | description = "Python package for providing Mozilla's CA Bundle." 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 11 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 12 | ] 13 | 14 | [[package]] 15 | name = "chardet" 16 | version = "3.0.4" 17 | description = "Universal encoding detector for Python 2 and 3" 18 | optional = false 19 | python-versions = "*" 20 | files = [ 21 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 22 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 23 | ] 24 | 25 | [[package]] 26 | name = "colorama" 27 | version = "0.4.4" 28 | description = "Cross-platform colored terminal text." 29 | optional = false 30 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 31 | files = [ 32 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 33 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 34 | ] 35 | 36 | [[package]] 37 | name = "docker" 38 | version = "4.4.0" 39 | description = "A Python library for the Docker Engine API." 40 | optional = false 41 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 42 | files = [ 43 | {file = "docker-4.4.0-py2.py3-none-any.whl", hash = "sha256:317e95a48c32de8c1aac92a48066a5b73e218ed096e03758bcdd799a7130a1a1"}, 44 | {file = "docker-4.4.0.tar.gz", hash = "sha256:cffc771d4ea1389fc66bc95cb72d304aa41d1a1563482a9a000fba3a84ed5071"}, 45 | ] 46 | 47 | [package.dependencies] 48 | pywin32 = {version = "227", markers = "sys_platform == \"win32\""} 49 | requests = ">=2.14.2,<2.18.0 || >2.18.0" 50 | six = ">=1.4.0" 51 | websocket-client = ">=0.32.0" 52 | 53 | [package.extras] 54 | ssh = ["paramiko (>=2.4.2)"] 55 | tls = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"] 56 | 57 | [[package]] 58 | name = "halo" 59 | version = "0.0.31" 60 | description = "Beautiful terminal spinners in Python" 61 | optional = false 62 | python-versions = ">=3.4" 63 | files = [ 64 | {file = "halo-0.0.31-py2-none-any.whl", hash = "sha256:5350488fb7d2aa7c31a1344120cee67a872901ce8858f60da7946cef96c208ab"}, 65 | {file = "halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6"}, 66 | ] 67 | 68 | [package.dependencies] 69 | colorama = ">=0.3.9" 70 | log-symbols = ">=0.0.14" 71 | six = ">=1.12.0" 72 | spinners = ">=0.0.24" 73 | termcolor = ">=1.1.0" 74 | 75 | [package.extras] 76 | ipython = ["IPython (==5.7.0)", "ipywidgets (==7.1.0)"] 77 | 78 | [[package]] 79 | name = "idna" 80 | version = "2.10" 81 | description = "Internationalized Domain Names in Applications (IDNA)" 82 | optional = false 83 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 84 | files = [ 85 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 86 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 87 | ] 88 | 89 | [[package]] 90 | name = "log-symbols" 91 | version = "0.0.14" 92 | description = "Colored symbols for various log levels for Python" 93 | optional = false 94 | python-versions = "*" 95 | files = [ 96 | {file = "log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca"}, 97 | {file = "log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556"}, 98 | ] 99 | 100 | [package.dependencies] 101 | colorama = ">=0.3.9" 102 | 103 | [[package]] 104 | name = "loguru" 105 | version = "0.5.3" 106 | description = "Python logging made (stupidly) simple" 107 | optional = false 108 | python-versions = ">=3.5" 109 | files = [ 110 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, 111 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, 112 | ] 113 | 114 | [package.dependencies] 115 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 116 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 117 | 118 | [package.extras] 119 | dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] 120 | 121 | [[package]] 122 | name = "python-dateutil" 123 | version = "2.8.1" 124 | description = "Extensions to the standard Python datetime module" 125 | optional = false 126 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 127 | files = [ 128 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 129 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 130 | ] 131 | 132 | [package.dependencies] 133 | six = ">=1.5" 134 | 135 | [[package]] 136 | name = "pywin32" 137 | version = "227" 138 | description = "Python for Window Extensions" 139 | optional = false 140 | python-versions = "*" 141 | files = [ 142 | {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, 143 | {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, 144 | {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, 145 | {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, 146 | {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, 147 | {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, 148 | {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, 149 | {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, 150 | {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, 151 | {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, 152 | {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, 153 | {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, 154 | ] 155 | 156 | [[package]] 157 | name = "requests" 158 | version = "2.25.0" 159 | description = "Python HTTP for Humans." 160 | optional = false 161 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 162 | files = [ 163 | {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, 164 | {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, 165 | ] 166 | 167 | [package.dependencies] 168 | certifi = ">=2017.4.17" 169 | chardet = ">=3.0.2,<4" 170 | idna = ">=2.5,<3" 171 | urllib3 = ">=1.21.1,<1.27" 172 | 173 | [package.extras] 174 | security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] 175 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 176 | 177 | [[package]] 178 | name = "six" 179 | version = "1.15.0" 180 | description = "Python 2 and 3 compatibility utilities" 181 | optional = false 182 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 183 | files = [ 184 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 185 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 186 | ] 187 | 188 | [[package]] 189 | name = "spinners" 190 | version = "0.0.24" 191 | description = "Spinners for terminals" 192 | optional = false 193 | python-versions = "*" 194 | files = [ 195 | {file = "spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98"}, 196 | {file = "spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f"}, 197 | ] 198 | 199 | [[package]] 200 | name = "termcolor" 201 | version = "1.1.0" 202 | description = "ANSII Color formatting for output in terminal." 203 | optional = false 204 | python-versions = "*" 205 | files = [ 206 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 207 | ] 208 | 209 | [[package]] 210 | name = "urllib3" 211 | version = "1.26.5" 212 | description = "HTTP library with thread-safe connection pooling, file post, and more." 213 | optional = false 214 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 215 | files = [ 216 | {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, 217 | {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, 218 | ] 219 | 220 | [package.extras] 221 | brotli = ["brotlipy (>=0.6.0)"] 222 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] 223 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 224 | 225 | [[package]] 226 | name = "websocket-client" 227 | version = "0.57.0" 228 | description = "WebSocket client for Python. hybi13 is supported." 229 | optional = false 230 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 231 | files = [ 232 | {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, 233 | {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, 234 | ] 235 | 236 | [package.dependencies] 237 | six = "*" 238 | 239 | [[package]] 240 | name = "win32-setctime" 241 | version = "1.0.3" 242 | description = "A small Python utility to set file creation time on Windows" 243 | optional = false 244 | python-versions = ">=3.5" 245 | files = [ 246 | {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, 247 | {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, 248 | ] 249 | 250 | [package.extras] 251 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 252 | 253 | [metadata] 254 | lock-version = "2.0" 255 | python-versions = "^3.7" 256 | content-hash = "667cf69f3197c5dbdfea64d8672fc3a5f411f1be165eff83a469ec3e1b70e06e" 257 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "docker_sim" 3 | version = "1.0.0" 4 | description = "Light-weight framework to scale up simulations tasks with docker containers" 5 | authors = ["Michael Witttmann , Maximilan Speicher "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | docker = "^4.4.0" 11 | loguru = "^0.5.3" 12 | halo = "^0.0.31" 13 | python-dateutil = "^2.8.1" 14 | 15 | [tool.poetry.dev-dependencies] 16 | 17 | [build-system] 18 | requires = ["poetry-core>=1.0.0"] 19 | build-backend = "poetry.core.masonry.api" 20 | --------------------------------------------------------------------------------