{"id":20074271,"url":"https://github.com/juniper/juniper-pytest","last_synced_at":"2025-03-02T12:26:32.178Z","repository":{"id":38182704,"uuid":"232412981","full_name":"Juniper/juniper-pytest","owner":"Juniper","description":"Juniper Pytest is an automation developer's toolkit. It provides everything an automation developer might need to automate configuration and validation of Juniper network equipment with Python and Ansible.","archived":false,"fork":false,"pushed_at":"2022-12-08T03:26:50.000Z","size":956,"stargazers_count":17,"open_issues_count":11,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-01-13T00:33:56.723Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Juniper.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-07T20:39:35.000Z","updated_at":"2024-12-26T18:55:25.000Z","dependencies_parsed_at":"2023-01-24T07:45:22.022Z","dependency_job_id":null,"html_url":"https://github.com/Juniper/juniper-pytest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Juniper%2Fjuniper-pytest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Juniper%2Fjuniper-pytest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Juniper%2Fjuniper-pytest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Juniper%2Fjuniper-pytest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Juniper","download_url":"https://codeload.github.com/Juniper/juniper-pytest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241505094,"owners_count":19973349,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T14:50:11.710Z","updated_at":"2025-03-02T12:26:32.159Z","avatar_url":"https://github.com/Juniper.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# __pytest__ Container\n\n- [__pytest__ Container](#pytest-container)\n  - [__pytest__ Features](#pytest-features)\n    - [Packaged pytest plugins](#packaged-pytest-plugins)\n      - [Test Fixtures and Styles](#test-fixtures-and-styles)\n      - [Reporting](#reporting)\n      - [Test Execution](#test-execution)\n    - [Packaged python3 modules](#packaged-python3-modules)\n  - [__Ansible__ Features](#ansible-features)\n  - [__Usage__](#usage)\n    - [Mounting Tests and Output](#mounting-tests-and-output)\n    - [Forking the Container](#forking-the-container)\n    - [Organizing Tests](#organizing-tests)\n    - [Development Environment](#development-environment)\n    - [__Examples__](#examples)\n      - [Setup: Installing a VSRX Topology for Demo purposes](#setup-installing-a-vsrx-topology-for-demo-purposes)\n        - [Supported Vagrant Topologies](#supported-vagrant-topologies)\n        - [Usage](#usage)\n      - [PyEZ Connectivity Testing](#pyez-connectivity-testing)\n      - [JSNAPy Audit/Diff](#jsnapy-auditdiff)\n      - [Reporting Examples](#reporting-examples)\n        - [CSV](#csv)\n        - [HTML](#html)\n        - [JSON](#json)\n        - [Coverage](#coverage)\n        - [Test Exectution Time](#test-exectution-time)\n        - [Style](#style)\n        - [Static Analysis](#static-analysis)\n      - [Packaging and Distributing pytest Tests](#packaging-and-distributing-pytest-tests)\n\nContainer for executing pytest tests and Ansible roles on network devices, especially (our favorite) Juniper network devices.\n\nThe test framework [pytest](https://docs.pytest.org/en/latest/) is a simple yet powerful developer-friendly test framework that unleashes the full power of [Python 3](https://docs.python-guide.org) and [Ansible](https://docs.ansible.com/ansible/latest/network/getting_started/index.html) to automate network validation.\n\n\n## __pytest__ Features\n\n![pytest](images/pytest-logo.png)\n\nThe [pytest test automation framework](https://docs.pytest.org/en/latest/) provides a rich set of capabilities including:\n\n1. Detailed info on failing assert statements (no need to remember self.assert* names);\n2. Auto-discovery of test modules and functions;\n3. Modular fixtures for managing small or parametrized long-lived test resources;\n4. Rich plugin architecture, with over 315+ external plugins and thriving community;\n\nThis container packs the plugins and libraries that are well-supported and particularly useful for Network Automation, including:\n\n### Packaged pytest plugins\n\n#### Test Fixtures and Styles\n\n|  module  |  purpose |\n|---|---|\n| [pytest-ansible](https://github.com/ansible/pytest-ansible/blob/master/README.md)         | Several fixtures for reading [Ansible](https://docs.ansible.com/ansible/latest/network/getting_started/index.html) inventory (including dynamically generated inventory), running [Ansible](https://docs.ansible.com/ansible/latest/network/getting_started/index.html) modules, or inspecting [ansible_facts](https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variables-discovered-from-systems-facts).                                 |\n| [pytest-ansible-playbook-runner](https://github.com/final-israel/pytest-ansible-playbook-runner/blob/master/README.md)         | Run [Ansible playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks.html) during setup, execution and teardown phases of a test case.  |\n| [pytest-bdd](https://github.com/pytest-dev/pytest-bdd/blob/master/README.rst)             | Implements a subset of the Gherkin language to enable automating project requirements testing in natural language.  If you really liked Robot keywords, you'll love this.        |\n| [pytest-variables](https://github.com/pytest-dev/pytest-variables/blob/master/README.rst) | Provides variables to tests/fixtures as a dictionary via a file specified on the command line. YAML support provided out of the box.  Use if you want to avoid usage of Ansible. |\n\n#### Reporting\n\n| module                                                                                              | purpose                                                                       |\n| --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| [pytest-csv](https://github.com/nicoulaj/pytest-csv/blob/master/README.rst)                         | CSV output for pytest.                                                        |\n| [pytest-html](https://pypi.org/project/pytest-html/1.6/)                                            | Generates a HTML report for the test results.                                 |\n| [pytest-json-report](https://github.com/numirias/pytest-json-report)                                | Creates test reports as JSON.                                                 |\n| [pytest-cov](https://github.com/pytest-dev/pytest-cov/blob/master/README.rst)                       | Produces coverage reports.                                                    |\n| [pytest-pycodestyle](https://github.com/henry0312/pytest-codestyle/blob/master/README.md)           | Style checker for pytest code.                                                |\n| [pytest-flakes](https://github.com/fschulze/pytest-flakes/blob/master/README.rst)                   | Static analyzer for pytest code  to check for coding bugs prior to execution. |\n| [pytest-excel](https://github.com/ssrikanta/pytest-excel/blob/master/README.rst)                         | Excel output for pytest.                                                        |\n\n#### Test Execution\n\n| module                                                                                        | purpose                                                          |\n| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |\n| [pytest-atomic](https://github.com/megachweng/pytest-atomic/blob/master/README.md)            | Skip rest of tests if previous test failed.                      |\n| [pytest-xdist](https://github.com/pytest-dev/pytest-xdist/blob/master/README.rst)             | A pytest distributed testing plugin for parallel test execution. |\n| [pytest-shutil](https://github.com/manahl/pytest-plugins/blob/master/pytest-shutil/README.md) | A set of useful shell utiltity commands and settings.            |\n\n### Packaged python3 modules\n\n|module|purpose|\n|---|---|\n|[ansible](https://docs.ansible.com/ansible/latest/network/getting_started/index.html)|Extensible framework for automated configuration of network-connected devices and systems.  Inventory and playbooks are fully expose to pytest code via the [pytest-ansible](https://github.com/ansible/pytest-ansible/blob/master/README.md) plugin.|\n|[junos-eznc](https://github.com/Juniper/py-junos-eznc/blob/master/README.md)|Junos PyEZ is a Python library to remotely manage/automate Junos devices.|\n|[jsnapy](https://github.com/Juniper/jsnapy/blob/master/README.md)|Junos Snapshot Administrator enables you to capture and audit runtime environment snapshots of your networked devices running the Junos operating system (Junos OS).|\n|[napalm](https://github.com/napalm-automation/napalm/blob/develop/README.md)|NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is a Python library that implements a set of functions to interact with different router vendor devices using a unified API.|\n|[napalm-ansible](https://github.com/napalm-automation/napalm-ansible/blob/develop/README.md)|Collection of ansible modules that use napalm to retrieve data or modify configuration on networking devices.  Especially useful for non-Juniper devices.|\n|[pexpect](https://pexpect.readthedocs.io/en/stable/)|If you must screen-scrape to remote control an old network device (or enable netconf on a non-Juniper device), this may be a lifesaver.|\n|[ixnetwork-restpy](https://www.openixia.com/tutorials?subject=ixNetwork/restApi\u0026page=restPy.html)|A complete REST API to programatically control [IXIA traffic generators](https://www.ixiacom.com/products/test).  Full local documentation is available in [.venv/lib/python3.7/site-packages/ixnetwork_restpy/docs/index.html](.venv/lib/python3.7/site-packages/ixnetwork_restpy/docs/index.html) if you run ```make .venv```|\n|[stcrestclient](https://github.com/Spirent/py-stcrestclient/blob/master/README.md)|STC Rest API client to control [Spirent traffic generators](https://www.spirent.com/products-for/high-speed-network-testing).|\n|[sshtunnel](https://github.com/pahaz/sshtunnel/blob/master/README.rst)|Library to simplify connectivity to remote services via ssh tunneling.|\n\n## __Ansible__ Features\n\n![Ansible](images/ansible-logo.png)\n\nAnsible is a staple in network automation.  Many customers already have their [inventory in an Ansible-friendly yaml or ini](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) format.  For customers that haven't, we recommend that we steer our customers in this direction.  It's easy enough to [generate an inventory dynamically via a script](https://docs.ansible.com/ansible/latest/user_guide/intro_dynamic_inventory.html) as well from a database or any other static format.  Once this information is available as an Ansible inventory, it can be leveraged in *any* Ansible script or pytest network validation.\n\nThe standard [Ansible distribution comes standard with modules to manage network devices](https://docs.ansible.com/ansible/latest/modules/list_of_network_modules.html) from many major network equipment providers, including, most-importantly [Junos devices](https://docs.ansible.com/ansible/latest/modules/list_of_network_modules.html#junos).\n\n## __Usage__\n\nThis container can be used two basic ways:\n\n1. Mounting one or two volumes, one for tests and another for test results.\n2. Fork this repo.  Your tests should go into a tests directory.  A volume should be mounted to place the test results.  This allows you to add new python modules.  These python modules may contain standard test suites that are added to the test container.\n\n### Mounting Tests and Output\n\nMount the tests you want to execute if you want to use a pre-built container and you're sure the pre-built container provides all necessary dependencies.  All python code named ```test_*``` will be scanned for tests.  \n\nAny argument can be supplied via ```pytest.ini``` or as arguments to the ```docker run``` command.\n\nIf you want to be sure that your container has all the dependencies required to execute at build time\n\n1. The container can be used as-is by mounting test could (files names ```test_*```) into /tests in the container.  Any argument can be supplied via ```pytest.ini``` or as arguments to the ```docker run``` command.\n2. In the ```pytest.ini``` packaged with your tests, you can specify all options, including the expected test output directory.  By convention, this can be in a directory named \n\n### Forking the Container\n\n### Organizing Tests\n\nNote that it's also possible to organize tests in python modules that can be packaged and imported as dependencies from an (internal) pypi repo.\n\n### Development Environment\n\nYou must have the following:\n\n1. bash\n2. docker\n3. make\n4. Python 3.6 or better\n\nTo set up your development environment, simply run ```make test```.\n\nIf you wish to try running py.test interactively on the command line, you'll need to do the following _additional_ steps:\n\n1. ```export PIPENV_VENV_IN_PROJECT=1```\n2. ```export PIPENV_VERBOSITY=-1```\n3. ```export ANSIBLE_PYTHON_INTERPRETER=python3```\n\nThe ```export``` lines can be added to your profile (e.g.-- ```~/.bash_profile```), so you never have to set them again.\n\nTests should be run from the pipenv shell.  You can do this as follows (If you look at the ```Makefile``` target ```test```, it essentially does the same thing):\n\n1. ```pipenv shell```\n2. ```cd tests```\n3. ```py.test```\n\n### __Examples__\n\n#### Setup: Installing a VSRX Topology for Demo purposes\n\nFor this demo, you will need to have the following additional software installed on your computer:\n\n1. VirtualBox\n2. Vagrant\n\nThe [vqfx10k-vagrant](https://github.com/Juniper/vqfx10k-vagrant.git) repo is included as a subtree in the ```examples/vqfx10k-vagrant``` directory.  Make targets have been added for each of the topologies to assist you with test development.\n\n##### Supported Vagrant Topologies\n\n|Name|Purpose|Packet Forwarding Engine (PFE)|Ansible Provisioning|RAM|CPU|\n|---|---|---|---|---|---|\n|[light-1qfx](https://github.com/Juniper/vqfx10k-vagrant/blob/master/light-1qfx/README.md)|Single vqfx.|no|no|1G|1|\n|[light-2qfx](https://github.com/Juniper/vqfx10k-vagrant/blob/master/light-2qfx/README.md)|Two vqfx connected back-to-back.|no|yes|2G|2|\n|[light-2qfx-2srv](https://github.com/Juniper/vqfx10k-vagrant/blob/master/light-2qfx-2srv/README.md)|Two vqfx connected back-to-back each with one ubuntu server attached.|no|yes|3G|1|\n|[light-ipfabric-2S-3L](https://github.com/Juniper/vqfx10k-vagrant/blob/master/light-ipfabric-2S-3L/README.md)|Spine and leaf topology with two spines, three leaves, and an ubuntu server attached to each leaf.|no|yes|7G|2|\n|[full-1qfx](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-1qfx/README.md)|Single vqfx.|yes|no|3G|2|\n|[full-2qfx](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-2qfx/README.md)|Two vqfx connected back-to-back|yes|yes|5G|3|\n|[full-4qfx](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-4qfx/README.md)|Four vqfx connected in a full-mesh.|yes|yes|10G|5|\n|[full-1qfx-1srv](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-1qfx-1srv/README.md)|Single vqfx with an ubuntu server attached.|yes|yes|3G|2|\n|[full-2qfx-4srv-evpnvxlan](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-2qfx-4srv-evpnvxlan/README.md)|Two vqfx, attached back-to-back, each with two ubuntu servers attached.|yes|yes|8G|3|\n|[full-ipfabric-2S-3L](https://github.com/Juniper/vqfx10k-vagrant/blob/master/full-ipfabric-2S-3L/README.md)|Spine and leaf topology with two spines, three leaves, and an ubuntu server attached to each leaf.|yes|yes|16G|4|\n\nFor more information on the supported topologies, click the link in the _Name_ column above.\n\n##### Usage\n\nEach topology has three make target actions: ```up```,```down``` and ```destroy```.  To form the target name simple prepend the target action to the topology name.  \n\n__To bring up and provision the topology ```light-2qfx```, simply run:__\n\n``` bash\nmake up-light-2qfx\n```\n\nIf the topology support Ansible provisioning, then a provisioning script is run after the system(s) come up.  The inventory file can be found in topology directory, under ```.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory```.  It's in ```ini``` format.  This can be used for subsequent Ansible playbooks, and as inventory for ```pytest```.\n\nTo stop (but not destroy) it:\n\n``` bash\nmake halt-light-2qfx\n```\n\nTo destroy it:\n\n``` bash\nmake destroy-light-2qfx\n```\n\n#### PyEZ Connectivity Testing\n\n#### JSNAPy Audit/Diff\n\n#### Reporting Examples\n\n##### CSV\n\n``` bash\n$ pipenv run py.test --csv=report.csv\n...\n$ cat report.csv\nid,module,name,file,doc,markers,status,message,duration\nexamples/test_2qfx.py::test_get_inventory,examples.test_2qfx,test_get_inventory,examples/test_2qfx.py,,,passed,,0.11594510078430176\nexamples/test_2qfx.py::test_junos_alarms,examples.test_2qfx,test_junos_alarms,examples/test_2qfx.py,,,passed,,1.038262128829956\nexamples/test_2qfx.py::test_interfaces_up,examples.test_2qfx,test_interfaces_up,examples/test_2qfx.py,,,passed,,0.4928560256958008\nexamples/test_2qfx.py::test_junos_version,examples.test_2qfx,test_junos_version,examples/test_2qfx.py,,,passed,,0.0002608299255371094\n```\n\n##### HTML\n\nBy default, the style.css is generated into an ```assets``` folder in the same directory as the HTML report.  If you don't like that, use the ```--self-contained-html``` flag.\n\n``` bash\n$ pipenv run py.test --html=report.html  --self-contained--html\n```\n![pytest HTML report](images/report-html.png)\n\n##### JSON\n\nThe JSON report will dump out a JSON data blob that contain the results of all individual tests and what was logged.\n\n``` bash\n$ pipenv run py.test --json-report --json-report-file=report.json\n...\n$ jq .summary report.json\n{\n  \"passed\": 4,\n  \"total\": 4\n}\n```\n\nThere is also a ```--json-report-summary``` option to only indicate if the entire suite passed.\n\n##### Coverage\n\nThere are a variety of code coverage options.  Just specify a path to report code coverage on, and a report type.\n\n``` bash\n$ pipenv run py.test --cov=examples --cov-report=term --cov-branch\n...\n---------- coverage: platform darwin, python 3.7.4-final-0 -----------\nName                    Stmts   Miss Branch BrPart  Cover\n---------------------------------------------------------\nexamples/test_2qfx.py      37      0      2      0   100%\n```\n\n##### Test Exectution Time\n\nUse the ```---durations=0``` flag to make pytest report all test durations.  You can pass it a number to say the minimum slowest test you want the duration to be reported for.\n\n``` bash\n$ pipenv run py.test --durations=0\n...\n========================================= slowest test durations =========================================\n1.00s call     examples/test_2qfx.py::test_junos_alarms\n0.48s call     examples/test_2qfx.py::test_interfaces_up\n0.16s call     examples/test_2qfx.py::test_get_inventory\n\n(0.00 durations hidden.  Use -vv to show these durations.)\n======================================== 4 passed in 2.05 seconds ========================================\n```\n\n##### Style\n\nIt's highly recommended that you validate your code style during test execution to avoid errors.\n\n``` bash\n$ pipenv run py.test --codestyle\n============================================= test session starts ==============================================\nplatform darwin -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0\nansible: 2.8.2\nrootdir: /Users/ejacques/projects/Foundational/pytest, inifile: pytest.ini, testpaths: examples\nplugins: bdd-3.1.1, xdist-1.29.0, forked-1.0.2, flakes-4.0.0, csv-2.0.2, atomic-2.0.0, shutil-1.7.0, ansible-2.1.1, variables-1.7.1, ansible-playbook-runner-0.0.2, pycodestyle-1.4.0, cov-2.7.1, html-1.21.1, profiling-1.7.0, json-report-1.1.0, metadata-1.8.0\ncollected 5 items                                                                                              \n\nexamples/test_2qfx.py FAILED                                                                             [ 20%]\nexamples/test_2qfx.py::test_get_inventory PASSED                                                         [ 40%]\nexamples/test_2qfx.py::test_junos_alarms PASSED                                                          [ 60%]\nexamples/test_2qfx.py::test_interfaces_up PASSED                                                         [ 80%]\nexamples/test_2qfx.py::test_junos_version PASSED                                                         [100%]\n\n=================================================== FAILURES ===================================================\n______________________________________________ pycodestyle-check _______________________________________________\n/Users/ejacques/projects/Foundational/pytest/examples/test_2qfx.py:13:80: E501 line too long (113 \u003e 79 characters)\n            [\"ansible_host\", \"ansible_port\", \"ansible_user\", \"ansible_password\", \"ansible_ssh_private_key_file\"]}\n                                                                               ^\n/Users/ejacques/projects/Foundational/pytest/examples/test_2qfx.py:57:80: E501 line too long (100 \u003e 79 characters)\n        assert(len(chassis_alarms.xpath('//alarm-information/alarm-summary/no-active-alarms')) == 1)\n                                                                               ^\n/Users/ejacques/projects/Foundational/pytest/examples/test_2qfx.py:61:80: E501 line too long (99 \u003e 79 characters)\n        assert(len(system_alarms.xpath('//alarm-information/alarm-summary/no-active-alarms')) == 1)\n                                                                               ^\n\n====================================== 1 failed, 4 passed in 1.87 seconds ======================================\n```\n\n##### Static Analysis\n\nStatic analysis can also be run to check for potentially problematic code:\n\n``` bash\n$ pipenv run py.test --flakes\n============================================= test session starts =============================================\nplatform darwin -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0\nansible: 2.8.2\nrootdir: /Users/ejacques/projects/Foundational/pytest, inifile: pytest.ini, testpaths: examples\nplugins: bdd-3.1.1, xdist-1.29.0, forked-1.0.2, flakes-4.0.0, csv-2.0.2, atomic-2.0.0, shutil-1.7.0, ansible-2.1.1, variables-1.7.1, ansible-playbook-runner-0.0.2, pycodestyle-1.4.0, cov-2.7.1, html-1.21.1, profiling-1.7.0, json-report-1.1.0, metadata-1.8.0\ncollected 5 items                                                                                             \n\nexamples/test_2qfx.py FAILED                                                                            [ 20%]\nexamples/test_2qfx.py::test_get_inventory PASSED                                                        [ 40%]\nexamples/test_2qfx.py::test_junos_alarms PASSED                                                         [ 60%]\nexamples/test_2qfx.py::test_interfaces_up PASSED                                                        [ 80%]\nexamples/test_2qfx.py::test_junos_version PASSED                                                        [100%]\n\n================================================== FAILURES ===================================================\n_______________________________________________ pyflakes-check ________________________________________________\n/Users/ejacques/projects/Foundational/pytest/examples/test_2qfx.py:70: UnusedVariable\nlocal variable 'vqfx1' is assigned to but never used\n/Users/ejacques/projects/Foundational/pytest/examples/test_2qfx.py:73: UnusedVariable\nlocal variable 'vqfx2' is assigned to but never used\n===================================== 1 failed, 4 passed in 1.90 seconds ======================================\n```\n\n#### Packaging and Distributing pytest Tests\n\nSince the test suite is just Python code, you can simply package up tests or common test utilities as a Python module.  For that purpose, the [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/tutorial1.html#step-1-generate-a-python-package-project) project is available to easily generate a Python Package project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuniper%2Fjuniper-pytest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuniper%2Fjuniper-pytest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuniper%2Fjuniper-pytest/lists"}