├── tests ├── __init__.py └── test_durc.py ├── durc └── __init__.py ├── imgs ├── DURT.png ├── duct_mini.png ├── duck.svg └── DURT.svg ├── pyproject.toml ├── examples ├── dev_notes │ └── test_something.yaml └── single_play │ ├── case_positive_login_with_user.yaml │ └── the_same_in_python_for_comparison.py ├── .gitignore ├── README.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /durc/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /imgs/DURT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rochacbruno/DURC/HEAD/imgs/DURT.png -------------------------------------------------------------------------------- /imgs/duct_mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rochacbruno/DURC/HEAD/imgs/duct_mini.png -------------------------------------------------------------------------------- /tests/test_durc.py: -------------------------------------------------------------------------------- 1 | from durc import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "durc" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Bruno Rocha "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "3.7" 9 | ansible = "^2.8" 10 | ansible-runner = "^1.3" 11 | jinja2 = "^2.10" 12 | 13 | [tool.poetry.dev-dependencies] 14 | pytest = "^3.0" 15 | black = "*" 16 | flake8 = "^3.7" 17 | ipython = "^7.7" 18 | ipdb = "^0.12.2" 19 | 20 | [build-system] 21 | requires = ["poetry>=0.12"] 22 | build-backend = "poetry.masonry.api" 23 | -------------------------------------------------------------------------------- /examples/dev_notes/test_something.yaml: -------------------------------------------------------------------------------- 1 | #In [1]: from ansible_runner.runner import ansible_runner 2 | #In [2]: result = ansible_runner.run(playbook='./examples/simple/test_something.yaml', verbosity=True) 3 | #TASK [Assert that msg is World] ************************************************ 4 | #ok: [localhost] => { 5 | # "changed": false, 6 | # "msg": "All assertions passed" 7 | #} 8 | --- 9 | - hosts: localhost 10 | gather_facts: no 11 | vars: 12 | msg: World 13 | tasks: 14 | - name: Assert that msg is World 15 | assert: 16 | that: msg == 'World' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | .pytest_cache/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *.sqlite 8 | *.sqlite3 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | Pipfile.lock 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | .idea/ 64 | .idea 65 | .idea/* 66 | -------------------------------------------------------------------------------- /examples/single_play/case_positive_login_with_user.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: no 4 | vars: 5 | urls: 6 | login_page: http://localhost:8000/login 7 | tasks: 8 | 9 | - name: Meta 10 | block: 11 | - durc_metadata: 12 | id: 12345 13 | description: This case reproduces the user login workflow. 14 | level: Critical 15 | customerscenario: true 16 | - durc_skip: 17 | bugzilla_is_open: 456789 18 | 19 | - name: Arrange 20 | block: 21 | - name: Create a user `foo` on the web system 22 | durc_api: 23 | handler: myproduct.user.create # custom handlers by product 24 | data: username=foo password=bar 25 | cleanup: created_user.delete 26 | register: created_user 27 | 28 | - name: Act 29 | block: 30 | - name: Navigate to Login page 31 | durc_ui: 32 | open: "{{ urls.login_page }}" 33 | wait_for: "{{ css('#login_form') }}" 34 | register: login_form 35 | 36 | - name: Fill the Login Form 37 | durc_ui: 38 | object: login_form 39 | fill: 40 | username: created_user.username 41 | password: created_user.password.uncrypted 42 | submit: true 43 | register: response 44 | 45 | - name: Assert 46 | block: 47 | - name: Login was successful 48 | assert: 49 | that: "'Logged in as user' in response.content" 50 | ... 51 | -------------------------------------------------------------------------------- /examples/single_play/the_same_in_python_for_comparison.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Test module description""" 3 | import unittest 4 | import myproductlib 5 | 6 | from selenium import webdriver 7 | from some_module.decorators import skip_if, bug_is_open 8 | 9 | 10 | driver = driver = webdriver.Chrome() 11 | 12 | urls = {'login_page': 'http://localhost:8000/login'} 13 | 14 | 15 | class UserLoginTestCase(unittest.TestCase): 16 | """This class tests user login.""" 17 | 18 | def setUp(self): 19 | """Arrange step.""" 20 | self.created_user = myproductlib.user.create( 21 | username='foo', password='bar' 22 | ) 23 | 24 | def tearDown(self): 25 | """Arrange step teardown""" 26 | self.created_user.delete() 27 | 28 | @skip_if(bug_is_open("bugzilla", 456789)) 29 | def test_positive_login_with_user(self): 30 | """This case reproduces the user login workflow. 31 | 32 | :id: 12345 33 | :level: Critical 34 | :customerscenario: true 35 | """ 36 | # Act 37 | driver.get(urls['login_page']) 38 | 39 | username = driver.find_element_by_name("username") 40 | username.clear() 41 | username.send_keys(self.created_user.username) 42 | 43 | password = driver.find_element_by_name("password") 44 | password.clear() 45 | password.send_keys(self.created_user.password) 46 | 47 | driver.find_element_by_css("#login_form.input[type=submit]").click() 48 | 49 | # Assert 50 | self.assertIn("Logged in as user:", driver.get_attribute('inner_html')) 51 | -------------------------------------------------------------------------------- /imgs/duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Various Cliparts 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Architetto Francesco Rollandin 21 | 22 | 23 | 24 | 25 | Architetto Francesco Rollandin 26 | 27 | 28 | 29 | image/svg+xml 30 | 31 | 32 | en 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /imgs/DURT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 46 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 68 | 72 | 76 | 82 | DURC 93 | 97 | 102 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # This is archived 3 | 4 | **NEW PROJECT** -> https://github.com/rochacbruno/ansible-test 5 | 6 | ---- 7 | # DURC 8 | 9 | 10 |
11 | 12 | A **framework** and **specification** for writing and running automated declarative cases for testing or reproducibility. 13 | 14 | - **D**eclarative 15 | - **U**nderstandable 16 | - **R**eproducible 17 | - **C**ases 18 | 19 | ## Why does DURC exists 20 | 21 | Writing test cases is a task that requires at least the following skills: 22 | 23 | - Knowledge of the **product/components** being exercised. 24 | - Knowledge of automation **strategies**. 25 | - Knowledge of the **programming language** used for automation. 26 | - Knowledge of the **tools and frameworks**. 27 | - Knowledge of how the **metrics** are being gathered and reported. 28 | - Knowledge of how to prepare the **environment** to run the tests. 29 | - Knowledge on how to report reproducible **bug reports**. 30 | 31 | There is too much **Knowledge** one needs to master before starting 32 | creating automation. 33 | 34 | **DURC** aims to make testing easier, not only easier, but maintainable, reproducible and understandable by removing the need 35 | of some of knowledges above, so **Quality can be everyone's responsibility**, even the ones who are not testers/programmers. 36 | 37 | Using our idiom, we want **DURC** testing. 38 | 39 | - A **Declarative** (but strict) language for describing test scenarios. 40 | - That can be **Understable** by humans reading it. 41 | - And also **Reproducible** by automation engine (a.k.a: _Ansible_) 42 | 43 | ## Who is interested in DURC 44 | 45 | - Developer 46 | - To write automation while developing a feature and save time of collaboration to QA/QE. 47 | - CEE/Support 48 | - To reproduce customer cases, to report reproducible scenarios to Dev/QE 49 | - QA/QE 50 | - To reuse cases written by Customer/Dev/CEE, to write collaboratively new test scenarios. 51 | - Manager 52 | - To evaluate better the priority and value of cases that are Understandable. 53 | - Customer 54 | - To reproduce steps provided by CEE. 55 | 56 | ## How it works 57 | 58 | ### Engine 59 | 60 | **DURC** relies on the awesome **Ansible** so it follows the same principles, 61 | a **DURC** case is written in form of Ansible plays (that can be single playable yaml files or complete roles) with tasks using testing modules. 62 | 63 | ### Modules 64 | 65 | **DURC** relies on existing Ansible [testing Strategies](https://docs.ansible.com/ansible/latest/reference_appendices/test_strategies.html) + a set of specialized tools to provide the best test automation experience. 66 | 67 | Also it comes with wrappers for existing testing frameworks like py.test and selenium. 68 | 69 | ### Semantic 70 | 71 | As the name says, test scenarios must be **Declarative** in the same way the Ansible playbooks works, it must be described using YAML syntax and Ansible idioms and special syntax. 72 | 73 | The test scenarios must be also **Understandable** by humans so one would be able to open the YAML file and read it as it is a step by step manual of how to reproduce that scenario manually if needed. 74 | 75 | Also the yaml file should be **Reproducible** by automation, that means a person or an automation pipeline can use a pre defined container and just run against that declarative file with no issue. 76 | 77 | Example of a **Case**: 78 | 79 | 80 | `/case_repository_package_version.yaml` 81 | 82 | ```yaml 83 | - hosts: localhost 84 | gather_facts: no 85 | vars: 86 | package_repo: http://repos.org/foo/bar 87 | tasks: 88 | 89 | - name: any ansible module can be used here 90 | ... 91 | 92 | - name: Test repo contains package bar version 3.0.2 93 | durc: 94 | meta: 95 | component: repositories 96 | level: critical 97 | strategies: ['api', 'cli', 'ui'] 98 | skip_if: 99 | is_open: BZ456789 100 | arrange: 101 | - create: organization name=foo_org label=OrgFoo as foo_org 102 | act: 103 | - create: 104 | register: foo_repo 105 | repository: 106 | name: foo 107 | feed_url: "{ package_repo }" 108 | organization: foo_org 109 | - sync: 110 | download_policy: immediate 111 | repo: foo_repo 112 | assert: 113 | - contains foo_repo package_bar==3.0.2 114 | - eq: 115 | - foo_repo.name 116 | - foo 117 | 118 | - name: more ansible modules can be used 119 | ... 120 | 121 | # After running the results of `durc` module tasks will be included in the Junit.xml report. 122 | 123 | ``` 124 | 125 | Once **D**eclared and having the the product accessible on `http://localhost:8000` it should be easy to **U**understand and follow the steps ot to **R**eproduce the **Case** using: 126 | 127 | ```bash 128 | $ pip install ansible-durc 129 | $ durc reproduce case_repository_package_version.yaml 130 | 131 | # or 132 | 133 | $ dnf install docker 134 | $ docker run durc/durc reproduce case_repository_package_version.yaml 135 | ``` 136 | 137 | #### PROS 138 | 139 | - Readable by non programmers 140 | - Writable by non programers 141 | - Declarative 142 | - Abstracted 143 | - Strict (can be validated) 144 | - Effortless environment (ansible-runner container image) 145 | 146 | ------ 147 | 148 | ### Metrics 149 | 150 | **Ansible** can already generate **Junit** [files](https://docs.ansible.com/ansible/latest/plugins/callback/junit.html) 151 | 152 | ## FAQ 153 | 154 | ### Why not just write test scenarios in Python? 155 | 156 | #### it doesn't fit 157 | 158 | First, we are not talking only about **tests** we are referring to it as **cases** because sometimes it is more than tests, 159 | it can be reproducible solutions provided by CEE to Customer, By Dev to QE and also used to automate different kind of tasks in an RPA project. 160 | 161 | The same case described above could be written in Python, but that does not meet the **DURC** requirements: 162 | 163 | - **D**eclarative 164 | - Python tests are dependant of a programming flow and it is not trivial to make things idempotent and declarative. 165 | - **U**nderstandable 166 | - Non programmers cannot easily understand the steps to reproduce that case, also to write the tests it is required to know the programmming language, the frameworks, the rules. 167 | - **R**eproducible 168 | - To run single, isolated test cases written for example using Py.Test, one should prepare the whole environment which is not something one want to suggest a customer to do. 169 | - **C**ases 170 | - As said, not only tests, but any kind of automation case. 171 | 172 | #### Code has a cost 173 | 174 | There is a cost on maintaining code, together with the testing flow it is also needed to maintain the whole framework and tooling, we want **less code** and more **case automation**. 175 | 176 | Having a standard **framework** on top of a well known and robust engine (a.k.a _Ansible_) makes total sense. 177 | 178 | The end cost of maintaining, reviewing and reproducing **declarative** scenarios has been proved a good idea by Ansible itself. 179 | 180 | ### Why not adopt an Uniquitous language like Gherkin? 181 | 182 | #### Cost of maintainance 183 | 184 | Gherkin is a descriptive language for **BDD** and it requires that the syntax matches with tokens defined inside the project specification, for example, to use a `having login page opened do:` working, one will need to write a handler for `having` and `opened` tokens. We want **DURC** to be prodcut/project agnostic. 185 | 186 | #### Error prone 187 | 188 | It is not easy to keep a Gherkin code strictly right, it is easy to commit a typo. 189 | 190 | 191 | ### Who will develop and maintain the `durc_` modules? 192 | 193 | #### Does not matter! 194 | 195 | **DURC** is more like an specification and base modules, it is open to anyone to inherit from `durc_` std library to customize its own modules as long it follows the DURC specifications and rules. 196 | 197 | That means, for example, you have a product which delivers a REST api, you can use **durc_api** as base for your **durc_foo_api** and you can maintain it, host it etc. DURC will only need it to be discoverable under the Ansible virtual environment. (ex: `pip install durc_foo_api`). 198 | 199 | However, **DURC** will do the best to deliver the base modules for most common tasks in case automation like talking to APIs, CLIs, SSH client, Authentication methods, Conditionals, Fixtures and Reporting. 200 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "main" 3 | description = "Radically simple IT automation" 4 | name = "ansible" 5 | optional = false 6 | python-versions = "*" 7 | version = "2.8.3" 8 | 9 | [[package]] 10 | category = "main" 11 | description = "" 12 | name = "ansible-runner" 13 | optional = false 14 | python-versions = "*" 15 | version = "1.3.4" 16 | 17 | [package.dependencies] 18 | PyYAML = "*" 19 | pexpect = ">=4.5" 20 | psutil = "*" 21 | python-daemon = "*" 22 | six = "*" 23 | 24 | [[package]] 25 | category = "dev" 26 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 27 | name = "appdirs" 28 | optional = false 29 | python-versions = "*" 30 | version = "1.4.3" 31 | 32 | [[package]] 33 | category = "dev" 34 | description = "Disable App Nap on OS X 10.9" 35 | marker = "python_version >= \"3.4\" and sys_platform == \"darwin\" or sys_platform == \"darwin\"" 36 | name = "appnope" 37 | optional = false 38 | python-versions = "*" 39 | version = "0.1.0" 40 | 41 | [[package]] 42 | category = "dev" 43 | description = "Atomic file writes." 44 | name = "atomicwrites" 45 | optional = false 46 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 47 | version = "1.3.0" 48 | 49 | [[package]] 50 | category = "dev" 51 | description = "Classes Without Boilerplate" 52 | name = "attrs" 53 | optional = false 54 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 55 | version = "19.1.0" 56 | 57 | [[package]] 58 | category = "dev" 59 | description = "Specifications for callback functions passed in to an API" 60 | name = "backcall" 61 | optional = false 62 | python-versions = "*" 63 | version = "0.1.0" 64 | 65 | [[package]] 66 | category = "dev" 67 | description = "The uncompromising code formatter." 68 | name = "black" 69 | optional = false 70 | python-versions = ">=3.6" 71 | version = "19.3b0" 72 | 73 | [package.dependencies] 74 | appdirs = "*" 75 | attrs = ">=18.1.0" 76 | click = ">=6.5" 77 | toml = ">=0.9.4" 78 | 79 | [[package]] 80 | category = "dev" 81 | description = "Composable command line interface toolkit" 82 | name = "click" 83 | optional = false 84 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 85 | version = "7.0" 86 | 87 | [[package]] 88 | category = "dev" 89 | description = "Cross-platform colored terminal text." 90 | marker = "python_version >= \"3.4\" and sys_platform == \"win32\" or sys_platform == \"win32\"" 91 | name = "colorama" 92 | optional = false 93 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 94 | version = "0.4.1" 95 | 96 | [[package]] 97 | category = "dev" 98 | description = "Better living through Python with decorators" 99 | name = "decorator" 100 | optional = false 101 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 102 | version = "4.4.0" 103 | 104 | [[package]] 105 | category = "main" 106 | description = "Docutils -- Python Documentation Utilities" 107 | name = "docutils" 108 | optional = false 109 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 110 | version = "0.15.2" 111 | 112 | [[package]] 113 | category = "dev" 114 | description = "Discover and load entry points from installed packages." 115 | name = "entrypoints" 116 | optional = false 117 | python-versions = ">=2.7" 118 | version = "0.3" 119 | 120 | [[package]] 121 | category = "dev" 122 | description = "the modular source code checker: pep8, pyflakes and co" 123 | name = "flake8" 124 | optional = false 125 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 126 | version = "3.7.8" 127 | 128 | [package.dependencies] 129 | entrypoints = ">=0.3.0,<0.4.0" 130 | mccabe = ">=0.6.0,<0.7.0" 131 | pycodestyle = ">=2.5.0,<2.6.0" 132 | pyflakes = ">=2.1.0,<2.2.0" 133 | 134 | [[package]] 135 | category = "dev" 136 | description = "Read metadata from Python packages" 137 | name = "importlib-metadata" 138 | optional = false 139 | python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" 140 | version = "0.19" 141 | 142 | [package.dependencies] 143 | zipp = ">=0.5" 144 | 145 | [[package]] 146 | category = "dev" 147 | description = "IPython-enabled pdb" 148 | name = "ipdb" 149 | optional = false 150 | python-versions = ">=2.7" 151 | version = "0.12.2" 152 | 153 | [package.dependencies] 154 | setuptools = "*" 155 | 156 | [package.dependencies.ipython] 157 | python = ">=3.4" 158 | version = ">=5.1.0" 159 | 160 | [[package]] 161 | category = "dev" 162 | description = "IPython: Productive Interactive Computing" 163 | name = "ipython" 164 | optional = false 165 | python-versions = ">=3.5" 166 | version = "7.7.0" 167 | 168 | [package.dependencies] 169 | appnope = "*" 170 | backcall = "*" 171 | colorama = "*" 172 | decorator = "*" 173 | jedi = ">=0.10" 174 | pexpect = "*" 175 | pickleshare = "*" 176 | prompt-toolkit = ">=2.0.0,<2.1.0" 177 | pygments = "*" 178 | setuptools = ">=18.5" 179 | traitlets = ">=4.2" 180 | 181 | [[package]] 182 | category = "dev" 183 | description = "Vestigial utilities from IPython" 184 | name = "ipython-genutils" 185 | optional = false 186 | python-versions = "*" 187 | version = "0.2.0" 188 | 189 | [[package]] 190 | category = "dev" 191 | description = "An autocompletion tool for Python that can be used for text editors." 192 | name = "jedi" 193 | optional = false 194 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 195 | version = "0.15.0" 196 | 197 | [package.dependencies] 198 | parso = ">=0.5.0" 199 | 200 | [[package]] 201 | category = "main" 202 | description = "A small but fast and easy to use stand-alone template engine written in pure python." 203 | name = "jinja2" 204 | optional = false 205 | python-versions = "*" 206 | version = "2.10.1" 207 | 208 | [package.dependencies] 209 | MarkupSafe = ">=0.23" 210 | 211 | [[package]] 212 | category = "main" 213 | description = "Platform-independent file locking module" 214 | name = "lockfile" 215 | optional = false 216 | python-versions = "*" 217 | version = "0.12.2" 218 | 219 | [[package]] 220 | category = "main" 221 | description = "Safely add untrusted strings to HTML/XML markup." 222 | name = "markupsafe" 223 | optional = false 224 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 225 | version = "1.1.1" 226 | 227 | [[package]] 228 | category = "dev" 229 | description = "McCabe checker, plugin for flake8" 230 | name = "mccabe" 231 | optional = false 232 | python-versions = "*" 233 | version = "0.6.1" 234 | 235 | [[package]] 236 | category = "dev" 237 | description = "More routines for operating on iterables, beyond itertools" 238 | name = "more-itertools" 239 | optional = false 240 | python-versions = ">=3.4" 241 | version = "7.2.0" 242 | 243 | [[package]] 244 | category = "dev" 245 | description = "A Python Parser" 246 | name = "parso" 247 | optional = false 248 | python-versions = "*" 249 | version = "0.5.1" 250 | 251 | [[package]] 252 | category = "main" 253 | description = "Pexpect allows easy control of interactive console applications." 254 | name = "pexpect" 255 | optional = false 256 | python-versions = "*" 257 | version = "4.7.0" 258 | 259 | [package.dependencies] 260 | ptyprocess = ">=0.5" 261 | 262 | [[package]] 263 | category = "dev" 264 | description = "Tiny 'shelve'-like database with concurrency support" 265 | name = "pickleshare" 266 | optional = false 267 | python-versions = "*" 268 | version = "0.7.5" 269 | 270 | [[package]] 271 | category = "dev" 272 | description = "plugin and hook calling mechanisms for python" 273 | name = "pluggy" 274 | optional = false 275 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 276 | version = "0.12.0" 277 | 278 | [package.dependencies] 279 | importlib-metadata = ">=0.12" 280 | 281 | [[package]] 282 | category = "dev" 283 | description = "Library for building powerful interactive command lines in Python" 284 | name = "prompt-toolkit" 285 | optional = false 286 | python-versions = "*" 287 | version = "2.0.9" 288 | 289 | [package.dependencies] 290 | six = ">=1.9.0" 291 | wcwidth = "*" 292 | 293 | [[package]] 294 | category = "main" 295 | description = "Cross-platform lib for process and system monitoring in Python." 296 | name = "psutil" 297 | optional = false 298 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 299 | version = "5.6.3" 300 | 301 | [[package]] 302 | category = "main" 303 | description = "Run a subprocess in a pseudo terminal" 304 | name = "ptyprocess" 305 | optional = false 306 | python-versions = "*" 307 | version = "0.6.0" 308 | 309 | [[package]] 310 | category = "dev" 311 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 312 | name = "py" 313 | optional = false 314 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 315 | version = "1.8.0" 316 | 317 | [[package]] 318 | category = "dev" 319 | description = "Python style guide checker" 320 | name = "pycodestyle" 321 | optional = false 322 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 323 | version = "2.5.0" 324 | 325 | [[package]] 326 | category = "dev" 327 | description = "passive checker of Python programs" 328 | name = "pyflakes" 329 | optional = false 330 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 331 | version = "2.1.1" 332 | 333 | [[package]] 334 | category = "dev" 335 | description = "Pygments is a syntax highlighting package written in Python." 336 | name = "pygments" 337 | optional = false 338 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 339 | version = "2.4.2" 340 | 341 | [[package]] 342 | category = "dev" 343 | description = "pytest: simple powerful testing with Python" 344 | name = "pytest" 345 | optional = false 346 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 347 | version = "3.10.1" 348 | 349 | [package.dependencies] 350 | atomicwrites = ">=1.0" 351 | attrs = ">=17.4.0" 352 | colorama = "*" 353 | more-itertools = ">=4.0.0" 354 | pluggy = ">=0.7" 355 | py = ">=1.5.0" 356 | setuptools = "*" 357 | six = ">=1.10.0" 358 | 359 | [[package]] 360 | category = "main" 361 | description = "Library to implement a well-behaved Unix daemon process." 362 | name = "python-daemon" 363 | optional = false 364 | python-versions = "*" 365 | version = "2.2.3" 366 | 367 | [package.dependencies] 368 | docutils = "*" 369 | lockfile = ">=0.10" 370 | setuptools = "*" 371 | 372 | [[package]] 373 | category = "main" 374 | description = "YAML parser and emitter for Python" 375 | name = "pyyaml" 376 | optional = false 377 | python-versions = "*" 378 | version = "5.1.2" 379 | 380 | [[package]] 381 | category = "main" 382 | description = "Python 2 and 3 compatibility utilities" 383 | name = "six" 384 | optional = false 385 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 386 | version = "1.12.0" 387 | 388 | [[package]] 389 | category = "dev" 390 | description = "Python Library for Tom's Obvious, Minimal Language" 391 | name = "toml" 392 | optional = false 393 | python-versions = "*" 394 | version = "0.10.0" 395 | 396 | [[package]] 397 | category = "dev" 398 | description = "Traitlets Python config system" 399 | name = "traitlets" 400 | optional = false 401 | python-versions = "*" 402 | version = "4.3.2" 403 | 404 | [package.dependencies] 405 | decorator = "*" 406 | ipython-genutils = "*" 407 | six = "*" 408 | 409 | [[package]] 410 | category = "dev" 411 | description = "Measures number of Terminal column cells of wide-character codes" 412 | name = "wcwidth" 413 | optional = false 414 | python-versions = "*" 415 | version = "0.1.7" 416 | 417 | [[package]] 418 | category = "dev" 419 | description = "Backport of pathlib-compatible object wrapper for zip files" 420 | name = "zipp" 421 | optional = false 422 | python-versions = ">=2.7" 423 | version = "0.5.2" 424 | 425 | [metadata] 426 | content-hash = "98bccf55c1bfcf2083dc4b74443bc033673d3cc52995bf331ca088757ea49aad" 427 | python-versions = "3.7" 428 | 429 | [metadata.hashes] 430 | ansible = ["05f9ed3ca3e06dffaa87a73a8e6f7f322825bc3f609f8b71c4fe22dbbdf72abc"] 431 | ansible-runner = ["60f531f1cb9b2b0cb1e8cf8dc9f81186aecda22d01587f35c61a833d0198f220", "dafa8f28193ab253e71b07be3314f80d796672e1b96385f2c46c39b597c1e996"] 432 | appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] 433 | appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] 434 | atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] 435 | attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] 436 | backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"] 437 | black = ["09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", "68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"] 438 | click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] 439 | colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] 440 | decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"] 441 | docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] 442 | entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] 443 | flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] 444 | importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] 445 | ipdb = ["473fdd798a099765f093231a8b1fabfa95b0b682fce12de0c74b61a4b4d8ee57"] 446 | ipython = ["1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e", "537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d"] 447 | ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"] 448 | jedi = ["0e4ba6cb008377b5a3c015a99ca007711f22fd69b8d5ff9c1f07673aed512adb", "9f16cb00b2aee940df2efc1d7d7c848281fd16391536a3d4561f5aea49db1ee6"] 449 | jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] 450 | lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"] 451 | markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] 452 | mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] 453 | more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] 454 | parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"] 455 | pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"] 456 | pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", "9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"] 457 | pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] 458 | prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"] 459 | psutil = ["028a1ec3c6197eadd11e7b46e8cc2f0720dc18ac6d7aabdb8e8c0d6c9704f000", "503e4b20fa9d3342bcf58191bbc20a4a5ef79ca7df8972e6197cc14c5513e73d", "863a85c1c0a5103a12c05a35e59d336e1d665747e531256e061213e2e90f63f3", "954f782608bfef9ae9f78e660e065bd8ffcfaea780f9f2c8a133bb7cb9e826d7", "b6e08f965a305cd84c2d07409bc16fbef4417d67b70c53b299116c5b895e3f45", "bc96d437dfbb8865fc8828cf363450001cb04056bbdcdd6fc152c436c8a74c61", "cf49178021075d47c61c03c0229ac0c60d5e2830f8cab19e2d88e579b18cdb76", "d5350cb66690915d60f8b233180f1e49938756fb2d501c93c44f8fb5b970cc63", "eba238cf1989dfff7d483c029acb0ac4fcbfc15de295d682901f0e2497e6781a"] 460 | ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"] 461 | py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] 462 | pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] 463 | pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] 464 | pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] 465 | pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"] 466 | python-daemon = ["1d9a541f74822ac87045163ebd5473f9a7b22ab4031ed28293aba81fdaf6c156", "affeca9e5adfce2666a63890af9d6aff79f670f7511899edaddca7f96593cc25"] 467 | pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] 468 | six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] 469 | toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] 470 | traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"] 471 | wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] 472 | zipp = ["4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", "8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"] 473 | --------------------------------------------------------------------------------