├── .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 |
5 |
6 |
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 |
4 |
5 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
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 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/scalable_docker_simulation.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 |
--------------------------------------------------------------------------------