├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── pr-housekeeping.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .nvmrc ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc.json ├── .releaserc ├── .snyk ├── Contributor-Agreement.md ├── LICENSE ├── README.md ├── appveyor.yml ├── catalog-info.yaml ├── dev-requirements.txt ├── jest.config.js ├── lib ├── dependencies │ ├── build-dep-graph.ts │ ├── index.ts │ ├── inspect-implementation.ts │ ├── poetry.ts │ └── sub-process.ts ├── errors.ts ├── index.ts └── types.ts ├── package.json ├── pysrc ├── README.md ├── constants.py ├── distPackage.py ├── package.py ├── pip_resolve.py ├── pipfile.py ├── pkg_resources.py ├── pkg_resources_py2 │ ├── LICENSE │ ├── __init__.py │ ├── _vendor │ │ ├── __init__.py │ │ ├── appdirs.py │ │ ├── packaging │ │ │ ├── __about__.py │ │ │ ├── __init__.py │ │ │ ├── _compat.py │ │ │ ├── _structures.py │ │ │ ├── markers.py │ │ │ ├── requirements.py │ │ │ ├── specifiers.py │ │ │ ├── utils.py │ │ │ └── version.py │ │ ├── pyparsing.py │ │ └── six.py │ ├── extern │ │ └── __init__.py │ └── py31compat.py ├── pkg_resources_py3 │ ├── LICENSE │ ├── __init__.py │ ├── _vendor │ │ ├── __init__.py │ │ ├── appdirs.py │ │ ├── packaging │ │ │ ├── __about__.py │ │ │ ├── __init__.py │ │ │ ├── _manylinux.py │ │ │ ├── _musllinux.py │ │ │ ├── _structures.py │ │ │ ├── markers.py │ │ │ ├── requirements.py │ │ │ ├── specifiers.py │ │ │ ├── tags.py │ │ │ ├── utils.py │ │ │ └── version.py │ │ └── pyparsing.py │ └── extern │ │ └── __init__.py ├── pkg_resources_py3_12 │ ├── LICENSE │ └── __init__.py ├── pytoml │ ├── LICENSE │ ├── README.txt │ ├── __init__.py │ ├── core.py │ ├── parser.py │ └── writer.py ├── reqPackage.py ├── requirements │ ├── LICENSE │ ├── README.txt │ ├── __init__.py │ ├── fragment.py │ ├── parser.py │ ├── requirement.py │ └── vcs.py ├── setup_file.py ├── test_pip_resolve_py2.py ├── test_pip_resolve_py3.py ├── test_pip_resolve_py3_12.py ├── test_pipfile.py └── utils.py ├── setup.py ├── test ├── Dockerfile ├── fixtures │ ├── dence-dep-graph │ │ ├── expected.json │ │ └── pip_resolve_output.json │ ├── pipenv-project │ │ └── Pipfile │ ├── pipfile-without-dev-deps │ │ └── Pipfile │ ├── poetry-project │ │ ├── poetry.lock │ │ └── pyproject.toml │ ├── poetry-v2-project │ │ ├── poetry.lock │ │ └── pyproject.toml │ ├── setuptools-project │ │ └── setup.py │ ├── updated-manifest │ │ └── requirements.txt │ └── updated-manifests-with-python-markers │ │ └── requirements.txt ├── manual.js ├── system │ └── inspect.spec.ts ├── test-utils.ts ├── unit │ ├── build-args.spec.ts │ ├── fixtures │ │ └── requirements.txt │ ├── inspect-implementation.spec.ts │ ├── poetry.spec.ts │ ├── setup_file.spec.ts │ └── sub-process.spec.ts └── workspaces │ ├── pip-app-bom │ └── requirements.txt │ ├── pip-app-circular-deps │ └── requirements.txt │ ├── pip-app-deps-canonicalization │ └── requirements.txt │ ├── pip-app-deps-conditional │ └── requirements.txt │ ├── pip-app-deps-editable │ ├── .gitignore │ └── requirements.txt │ ├── pip-app-deps-not-installed │ └── requirements.txt │ ├── pip-app-deps-with-dashes │ └── requirements.txt │ ├── pip-app-deps-with-urls │ └── requirements.txt │ ├── pip-app-dev-alpha-beta-python-version │ └── requirements.txt │ ├── pip-app-local-dir │ ├── boto3 │ │ └── README.md │ └── requirements.txt │ ├── pip-app-local-nonexistent-file │ ├── lib │ │ └── nonexistent │ │ │ └── not-setup-file.txt │ └── requirements.txt │ ├── pip-app-local-whl-file │ ├── README.md │ ├── lib │ │ └── my_package-0.1.0-py3-none-any.whl │ ├── project │ │ └── setup.py │ └── requirements.txt │ ├── pip-app-optional-dependencies │ └── requirements.txt │ ├── pip-app-trusted-host │ └── requirements.txt │ ├── pip-app-with-openapi_spec_validator │ └── requirements.txt │ ├── pip-app-with-python-markers │ └── requirements.txt │ ├── pip-app-without-markupsafe │ └── requirements.txt │ ├── pip-app │ ├── packages │ │ └── prometheus_client-0.6.0 │ │ │ ├── PKG-INFO │ │ │ ├── README.md │ │ │ ├── prometheus_client │ │ │ ├── __init__.py │ │ │ ├── bridge │ │ │ │ ├── __init__.py │ │ │ │ └── graphite.py │ │ │ ├── context_managers.py │ │ │ ├── core.py │ │ │ ├── decorator.py │ │ │ ├── exposition.py │ │ │ ├── gc_collector.py │ │ │ ├── metrics.py │ │ │ ├── metrics_core.py │ │ │ ├── mmap_dict.py │ │ │ ├── multiprocess.py │ │ │ ├── openmetrics │ │ │ │ ├── __init__.py │ │ │ │ ├── exposition.py │ │ │ │ └── parser.py │ │ │ ├── parser.py │ │ │ ├── platform_collector.py │ │ │ ├── process_collector.py │ │ │ ├── registry.py │ │ │ ├── samples.py │ │ │ ├── twisted │ │ │ │ ├── __init__.py │ │ │ │ └── _exposition.py │ │ │ ├── utils.py │ │ │ └── values.py │ │ │ ├── setup.cfg │ │ │ └── setup.py │ └── requirements.txt │ ├── pipenv-app │ ├── Pipfile │ └── README │ ├── pipfile-empty │ └── Pipfile │ ├── pipfile-markers │ └── Pipfile │ ├── pipfile-nested-dirs │ ├── README │ └── nested │ │ └── directory │ │ └── Pipfile │ ├── pipfile-optional-dependencies │ └── Pipfile │ ├── pipfile-pipapp-pinned │ ├── Pipfile │ ├── Pipfile.lock │ └── README │ ├── pipfile-pipapp │ ├── Pipfile │ └── README │ ├── poetry-app-optional-dependencies │ ├── poetry.lock │ └── pyproject.toml │ ├── poetry-app-without-lockfile │ └── pyproject.toml │ ├── poetry-app │ ├── poetry.lock │ └── pyproject.toml │ ├── poetry-v2-app-optional-dependencies │ ├── poetry.lock │ └── pyproject.toml │ ├── poetry-v2-app │ ├── poetry.lock │ └── pyproject.toml │ ├── setup_py-app-optional-dependencies │ └── setup.py │ └── setup_py-app │ └── setup.py ├── tox.ini ├── tsconfig-test.json └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | node: circleci/node@7.1.0 5 | prodsec: snyk/prodsec-orb@1 6 | 7 | defaults: &defaults 8 | resource_class: medium 9 | docker: 10 | - image: cimg/node:19.6.1 11 | 12 | jobs: 13 | security-scans: 14 | resource_class: small 15 | <<: *defaults 16 | steps: 17 | - checkout 18 | - node/install-packages: 19 | with-cache: false 20 | override-ci-command: npm install 21 | - prodsec/security_scans: 22 | mode: auto 23 | open-source-additional-arguments: --exclude=test 24 | iac-scan: disabled 25 | 26 | lint: 27 | <<: *defaults 28 | steps: 29 | - checkout 30 | - node/install-packages: 31 | with-cache: false 32 | override-ci-command: npm install 33 | - run: 34 | command: npm run lint 35 | 36 | test: 37 | <<: *defaults 38 | parameters: 39 | node_version: 40 | type: string 41 | python_version: 42 | type: string 43 | steps: 44 | - checkout 45 | - setup_remote_docker 46 | - when: 47 | condition: 48 | equal: [ "3.12", <>] 49 | steps: 50 | - run: 51 | name: Run tests 52 | no_output_timeout: 30m 53 | command: | 54 | BUILDKIT_PROGRESS=plain \ 55 | DOCKER_BUILDKIT=1 \ 56 | docker build \ 57 | --build-arg NODE_VERSION=<< parameters.node_version >> \ 58 | --build-arg PYTHON_VERSION=<< parameters.python_version >> \ 59 | --build-arg PY_TEST_CMD=test:pysrc3_12 \ 60 | -t snyk-python-plugin:integration-tests-<< parameters.python_version >> \ 61 | -f test/Dockerfile . 62 | docker run --rm snyk-python-plugin:integration-tests-<< parameters.python_version >> 63 | - when: 64 | condition: 65 | or: 66 | - equal: [ "3.8", <>] 67 | - equal: [ "3.9", <>] 68 | - equal: [ "3.10", <>] 69 | - equal: [ "3.11", <>] 70 | steps: 71 | - run: 72 | name: Run tests 73 | no_output_timeout: 30m 74 | command: | 75 | BUILDKIT_PROGRESS=plain \ 76 | DOCKER_BUILDKIT=1 \ 77 | docker build \ 78 | --build-arg NODE_VERSION=<< parameters.node_version >> \ 79 | --build-arg PYTHON_VERSION=<< parameters.python_version >> \ 80 | --build-arg PY_TEST_CMD=test:pysrc3 \ 81 | -t snyk-python-plugin:integration-tests-<< parameters.python_version >> \ 82 | -f test/Dockerfile . 83 | docker run --rm snyk-python-plugin:integration-tests-<< parameters.python_version >> 84 | 85 | 86 | build: 87 | <<: *defaults 88 | steps: 89 | - checkout 90 | - node/install-packages: 91 | with-cache: false 92 | override-ci-command: npm install 93 | - run: 94 | command: npm run build 95 | 96 | release: 97 | <<: *defaults 98 | docker: 99 | - image: node:18 100 | steps: 101 | - checkout 102 | - node/install-packages: 103 | with-cache: false 104 | override-ci-command: npm install 105 | - run: 106 | command: npm run build 107 | - run: 108 | name: Release 109 | command: npx semantic-release@21 110 | 111 | workflows: 112 | version: 2 113 | test_and_release: 114 | jobs: 115 | - prodsec/secrets-scan: 116 | name: Scan repository for secrets 117 | context: 118 | - snyk-bot-slack 119 | channel: snyk-vuln-alerts-sca 120 | filters: 121 | branches: 122 | ignore: 123 | - main 124 | 125 | - security-scans: 126 | name: Security Scans 127 | context: 128 | - open_source-managed 129 | 130 | - lint: 131 | name: Lint 132 | filters: 133 | branches: 134 | ignore: 135 | - main 136 | 137 | - build: 138 | name: Build 139 | filters: 140 | branches: 141 | ignore: 142 | - main 143 | 144 | - test: 145 | name: Node << matrix.node_version >>, Python << matrix.python_version >> 146 | requires: 147 | - Lint 148 | - Build 149 | matrix: 150 | parameters: 151 | node_version: [ 152 | '24', 153 | '22', 154 | '20', 155 | ] 156 | python_version: [ 157 | '3.8', 158 | '3.9', 159 | '3.10', 160 | '3.11', 161 | '3.12', 162 | ] 163 | filters: 164 | branches: 165 | ignore: 166 | - main 167 | 168 | - release: 169 | name: Release 170 | context: nodejs-lib-release 171 | requires: 172 | - Security Scans 173 | filters: 174 | branches: 175 | only: 176 | - main 177 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .venvs/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "parserOptions": { 5 | "ecmaVersion": 6 6 | }, 7 | "env": { 8 | "node": true, 9 | "es6": true 10 | }, 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "prettier" 16 | ], 17 | "rules": { 18 | "@typescript-eslint/explicit-function-return-type": 0, 19 | "@typescript-eslint/no-explicit-any": 0, 20 | "@typescript-eslint/no-var-requires": 0, 21 | "@typescript-eslint/no-use-before-define": 0, 22 | "no-prototype-builtins": 0, 23 | "no-var": 2, 24 | "prefer-arrow-callback": 2, 25 | "prefer-const": 2, 26 | "require-atomic-updates": 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @snyk/os-managed 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Commit messages 4 | 5 | Commit messages must follow the [Angular-style](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) commit format (but excluding the scope). 6 | 7 | i.e: 8 | 9 | ```text 10 | fix: minified scripts being removed 11 | 12 | Also includes tests 13 | ``` 14 | 15 | This will allow for the automatic changelog to generate correctly. 16 | 17 | ### Commit types 18 | 19 | Must be one of the following: 20 | 21 | * **feat**: A new feature 22 | * **fix**: A bug fix 23 | * **docs**: Documentation only changes 24 | * **test**: Adding missing tests 25 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation 26 | * **refactor**: A code change that neither fixes a bug nor adds a feature 27 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 28 | * **perf**: A code change that improves performance 29 | 30 | To release a major you need to add `BREAKING CHANGE: ` to the start of the body and the detail of the breaking change. 31 | 32 | ## Code standards 33 | 34 | Ensure that your code adheres to the included `.eslintrc` config by running `npm run lint`. 35 | 36 | ## Sending pull requests 37 | 38 | - add tests for newly added code (and try to mirror directory and file structure if possible) 39 | - spell check 40 | - PRs will not be code reviewed unless all tests are passing (run `npm test`) 41 | 42 | *Important:* when fixing a bug, please commit a **failing test** first demonstrate the current code is failing. Once that commit is in place, then commit the bug fix, so that we can test *before* and *after*. 43 | 44 | Remember that you're developing for multiple platforms and versions of node, so if the tests pass on your Mac or Linux or Windows machine, it *may* not pass elsewhere. 45 | 46 | ## Contributor Agreement 47 | 48 | A pull-request will only be considered for merging into the upstream codebase after you have signed our [contributor agreement](https://github.com/snyk/snyk-python-plugin/blob/main/Contributor-Agreement.md), assigning us the rights to the contributed code and granting you a license to use it in return. If you submit a pull request, you will be prompted to review and sign the agreement with one click (we use [CLA assistant](https://cla-assistant.io/)). 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - `node -v`: 2 | - `npm -v`: 3 | - `snyk -v`: 4 | - Command run: 5 | 6 | ### Expected behaviour 7 | 8 | 9 | ### Actual behaviour 10 | 11 | 12 | ### Steps to reproduce 13 | 14 | 15 | --- 16 | 17 | If applicable, please append the `--debug` flag on your command and include the output here **ensuring to remove any sensitive/personal details or tokens. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] Ready for review 2 | - [ ] Follows CONTRIBUTING rules 3 | - [ ] Reviewed by Snyk internal team 4 | 5 | #### What does this PR do? 6 | 7 | 8 | #### Where should the reviewer start? 9 | 10 | 11 | #### How should this be manually tested? 12 | 13 | 14 | #### Any background context you want to provide? 15 | 16 | 17 | #### What are the relevant tickets? 18 | 19 | 20 | #### Screenshots 21 | 22 | 23 | #### Additional questions 24 | -------------------------------------------------------------------------------- /.github/workflows/pr-housekeeping.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '0 0 * * *' # Every day at midnight 4 | workflow_dispatch: 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v7 11 | with: 12 | stale-pr-message: "Your PR has not had any activity for 60 days. In 7 days I'll close it. Make some activity to remove this." 13 | close-pr-message: "Your PR has now been stale for 7 days. I'm closing it." 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | __pycache__/ 3 | *.pyc 4 | npm-debug.log 5 | .idea 6 | 7 | # tox testing 8 | /.tox/ 9 | *.egg-info/ 10 | .venvs/ 11 | dist/ 12 | build-with-tests/ 13 | package-lock.json 14 | 15 | .nyc_output 16 | 17 | .eslintcache 18 | 19 | # Test output reports, coverage, etc 20 | reports/ 21 | 22 | # Generic venv directory for experimenting / testing 23 | venv/ 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .jscsrc 3 | .travis.yml 4 | /test 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.1 4 | hooks: 5 | - id: gitleaks 6 | 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .venvs/ 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "htmlWhitespaceSensitivity": "ignore" 6 | } 7 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "main", 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/github", 6 | "@semantic-release/npm", 7 | "@semantic-release/release-notes-generator" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.25.0 3 | # ignores vulnerabilities until expiry date; change duration by modifying expiry date 4 | ignore: 5 | 'snyk:lic:npm:shescape:MPL-2.0': 6 | - '*': 7 | reason: None Given 8 | expires: 2122-12-29T08:08:41.608Z 9 | created: 2022-11-29T08:08:41.611Z 10 | patch: {} 11 | 12 | -------------------------------------------------------------------------------- /Contributor-Agreement.md: -------------------------------------------------------------------------------- 1 | # Snyk CLI tool contributor agreement 2 | 3 | This Snyk CLI tool Agreement (this **"Agreement"**) applies to any Contribution you make to any Work. 4 | 5 | This is a binding legal agreement on you and any organization you represent. If you are signing this Agreement on behalf of your employer or other organization, you represent and warrant that you have the authority to agree to this Agreement on behalf of the organization. 6 | 7 | ## 1. Definitions 8 | 9 | **"Contribution"** means any original work, including any modification of or addition to an existing work, that you submit to Snyk CLI tool repo in any manner for inclusion in any Work. 10 | 11 | **"Snyk", "we"** and **"us"** means Snyk Ltd. 12 | 13 | **"Work"** means any project, work or materials owned or managed by Snyk Ltd. 14 | 15 | **"You"** and **"your"** means you and any organization on whose behalf you are entering this Agreement. 16 | 17 | ## 2. Copyright Assignment, License and Waiver 18 | 19 | **(a) Assignment.** By submitting a Contribution, you assign to Snyk all right, title and interest in any copright you have in the Contribution, and you waive any rights, including any moral rights, database rights, etc., that may affect your ownership of the copyright in the Contribution. 20 | 21 | **(b) License to Snyk.** If your assignment in Section 2(a) is ineffective for any reason, you grant to us and to any recipient of any Work distributed by use, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable licence to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Contributions and any derivative work created based on a Contribution. If your license grant is ineffective for any reason, you irrevocably waive and covenant to not assert any claim you may have against us, our successors in interest, and any of our direct or indirect licensees and customers, arising out of our, our successors in interest's, or any of our direct or indirect licensees' or customers' use, reproduction, preparation of derivative works, public display, public performance, sublicense, and distribution of a Contribution. You also agree that we may publicly use your name and the name of any organization on whose behalf you're entering into this Agreement in connection with publicizing the Work. 22 | 23 | **(c) License to you.** We grant to you a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute a Contribution and any derivative works you create based on a Contribution. 24 | 25 | ## 3. Patent License 26 | You grant to us and to any recipient of any Work distributed by us, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable patent license to make, have made, use, sell, offer to sell, import, and otherwise transfer the Contribution in whole or in part, along or included in any Work under any patent you own, or license from a third party, that is necessarily infringed by the Contribution or by combination of the Contribution with any Work. 27 | 28 | ## 4. Your Representation and Warranties. 29 | By submitting a Contribution, you represent and warrant that: (a) each Contribution you submit is an original work and you can legally grant the rights set out in this Agreement; (b) the Contribution does not, and any exercise of the rights granted by you will not, infringe any third party's intellectual property or other right; and (c) you are not aware of any claims, suits, or actions pertaining to the Contribution. You will notify us immediately if you become aware or have reason to believe that any of your representations and warranties is or becomes inaccurate. 30 | 31 | ##5. Intellectual Property 32 | Except for the assignment and licenses set forth in this Agreement, this Agreement does not transfer any right, title or interest in any intellectual property right of either party to the other. If you choose to provide us with suggestions, ideas for improvement, recommendations or other feedback, on any Work we may use your feedback without any restriction or payment. 33 | 34 | ## Miscellaneous 35 | English law governs this Agreement, excluding any applicable conflict of laws rules or principles, and the parties agree to the exclusive jurisdiction of the courts in England, UK. This Agreement does not create a partnership, agency relationship, or joint venture between the parties. We may assign this Agreement without notice or restriction. If any provision of this Agreement is unenforcable, that provision will be modified to render it enforceable to the extent possible to effect the parties' intention and the remaining provisions will not be affected. The parties may amend this Agreement only in a written amendment signed by both parties. This Agreement comprises the parties' entire agreement relating to the subject matter of this Agreement. 36 | 37 | **Agreed and accepted on my behalf and on behalf of my organization** 38 | 39 | Our contributor agreement is based on the [mongoDB contributor agreement] (https://www.mongodb.com/legal/contributor-agreement). 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Snyk Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Snyk logo](https://snyk.io/style/asset/logo/snyk-print.svg) 2 | 3 | *** 4 | 5 | Snyk helps you find, fix and monitor for known vulnerabilities in your dependencies, both on an ad hoc basis and as part of your CI (Build) system. 6 | 7 | | :information_source: This repository is only a plugin to be used with the Snyk CLI tool. To use this plugin to test and fix vulnerabilities in your project, install the Snyk CLI tool first. Head over to [snyk.io](https://github.com/snyk/snyk) to get started. | 8 | | --- | 9 | 10 | # Support 11 | 12 | - ❌ Not supported 13 | - ❓ No issues expected but not regularly tested 14 | - ✅ Supported and verified with tests 15 | 16 | ## Supported OS 17 | 18 | | OS | Supported | 19 | |--------|------------| 20 | | Windows| ✅ | 21 | | Linux | ✅ | 22 | | OSX | ️✅ | 23 | 24 | ## Supported Node versions 25 | 26 | | Node | Supported | 27 | |------|------------| 28 | | 12 | ✅ | 29 | | 14 | ✅ | 30 | | 16 | ✅ | 31 | 32 | ## Supported Pip & Python versions (requirements.txt) 33 | 34 | | Pip / Python |2.7|3.6|3.7|3.8|3.9|3.10|3.11|3.12| 35 | |----------------|---|---|---|---|---|---|---|---| 36 | | 10.0.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ 37 | | 18.1.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 38 | 39 | ## Supported Poetry versions (`pyproject.toml` and `poetry.lock`) 40 | All known versions are expected to be supported (current latest version is 1.1.6) 41 | 42 | ## Snyk Python CLI Plugin 43 | 44 | This plugin provides dependency metadata for Python projects that use one of the following dependency management methods: 45 | 46 | * `pip` with a `requirements.txt` file 47 | * `pipenv` with a `Pipfile` file 48 | * `poetry` with `pyproject.toml` and `poetry.lock` 49 | 50 | There's a special `only-provenance` mode that allows extracting of top-level dependencies with 51 | their corresponding positions in the original manifest file. 52 | 53 | ## Contributing 54 | 55 | [Guide](https://github.com/snyk/snyk-python-plugin/blob/main/.github/CONTRIBUTING.md) 56 | 57 | ### Developing and Testing 58 | 59 | Prerequisites: 60 | - Node.js 10+ 61 | - Python 2.7 or 3.6+ 62 | - Installed outside of any virtualenv: 63 | - [pip](https://pip.pypa.io/en/stable/installing/) 64 | - the contents of `dev-requirements.txt`: 65 | ``` 66 | pip install --user -r dev-requirements.txt 67 | ``` 68 | - if in linux, `python-dev` installed with apt, or see [here](https://stackoverflow.com/a/21530768). 69 | 70 | Tests can be run against multiple python versions by using tox: 71 | 72 | ``` 73 | pip install tox 74 | tox 75 | ``` 76 | 77 | Linting and testing: 78 | ``` 79 | npm i 80 | npm run lint 81 | npm run test 82 | ``` 83 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # https://www.appveyor.com/docs/appveyor-yml 2 | 3 | # to disable automatic builds 4 | build: off 5 | branches: 6 | only: 7 | - main 8 | 9 | init: 10 | - git config --global core.autocrlf true 11 | 12 | shallow_clone: true 13 | clone_depth: 1 14 | 15 | cache: 16 | - node_modules -> package.json 17 | 18 | environment: 19 | matrix: 20 | - nodejs_version: "12" 21 | - nodejs_version: "10" 22 | - nodejs_version: "8" 23 | 24 | matrix: 25 | fast_finish: true 26 | 27 | install: 28 | - ps: Install-Product node $env:nodejs_version 29 | - node --version 30 | - npm --version 31 | - pip install tox 32 | 33 | test_script: 34 | - tox 35 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: snyk-python-plugin 5 | annotations: 6 | github.com/project-slug: snyk/snyk-python-plugin 7 | github.com/team-slug: snyk/os-managed 8 | spec: 9 | type: snyk-cli-plugin 10 | lifecycle: "-" 11 | owner: os-managed 12 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pipenv==2022.4.8; python_version == '3.8' 2 | pipenv==2022.4.8; python_version == '3.9' 3 | pipenv==2022.4.8; python_version == '3.10' 4 | pipenv==2022.4.8; python_version == '3.11' 5 | pipenv==2023.11.15; python_version == '3.12' 6 | pipenv==2018.11.26; python_version == '2.7' 7 | virtualenv==20.15.1; python_version == '3.8' 8 | virtualenv==20.15.1; python_version == '3.9' 9 | virtualenv==20.15.1; python_version == '3.10' 10 | virtualenv==20.15.1; python_version == '3.11' 11 | virtualenv==20.25.0; python_version == '3.12' 12 | virtualenv==16.2.0; python_version == '2.7' 13 | mock 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | collectCoverage: false, // Run yarn test:coverage to generate coverage reports 6 | collectCoverageFrom: ['lib/**/*.ts'], 7 | coverageReporters: ['html', 'text-summary'], 8 | coverageDirectory: '/reports/coverage', 9 | testMatch: ['**/*.spec.ts'], // Remove when all tests are using Jest 10 | modulePathIgnorePatterns: ['/dist', ''], 11 | reporters: [ 12 | 'default', 13 | [ 14 | 'jest-junit', 15 | { 16 | outputDirectory: '/reports/jest', 17 | }, 18 | ], 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /lib/dependencies/build-dep-graph.ts: -------------------------------------------------------------------------------- 1 | import { DepGraph } from '@snyk/dep-graph'; 2 | import { depTreeToGraph, DepTree } from '@snyk/dep-graph/dist/legacy'; 3 | 4 | type PackageName = string; 5 | 6 | // This is a partially dep tree: every package exists once, all additional reference will have "true" as the value 7 | export interface PartialDepTree { 8 | name?: string; 9 | version?: string; 10 | dependencies?: Dependencies; 11 | labels?: { 12 | [key: string]: string; 13 | }; 14 | } 15 | 16 | type Dependencies = { 17 | [depName: string]: PartialDepTree | 'true'; 18 | }; 19 | 20 | export function buildDepGraph( 21 | partialDepTree: PartialDepTree, 22 | projectName?: string 23 | ): Promise { 24 | const packageToDepTreeMap = new Map(); 25 | 26 | const queue: Dependencies[] = [partialDepTree.dependencies]; 27 | const referencesToUpdate: { key: string; dependencies: Dependencies }[] = []; 28 | while (queue.length > 0) { 29 | const dependencies = queue.pop(); 30 | if (!dependencies) continue; 31 | 32 | for (const [key, dependencyDepTree] of Object.entries(dependencies)) { 33 | if (dependencyDepTree === 'true') { 34 | referencesToUpdate.push({ key, dependencies }); 35 | } else { 36 | packageToDepTreeMap.set(key, dependencyDepTree); 37 | queue.push(dependencyDepTree.dependencies); 38 | } 39 | } 40 | } 41 | 42 | referencesToUpdate.forEach(({ key, dependencies }) => { 43 | if (!packageToDepTreeMap.get(key)) { 44 | // this should never happen 45 | throw new Error(`key ${key} not found in packageToDepTreeMap`); 46 | } 47 | dependencies[key] = packageToDepTreeMap.get(key); 48 | }); 49 | 50 | if (projectName) partialDepTree.name = projectName; 51 | 52 | return depTreeToGraph(partialDepTree as DepTree, 'pip'); 53 | } 54 | -------------------------------------------------------------------------------- /lib/dependencies/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as subProcess from './sub-process'; 3 | 4 | import { legacyPlugin as api } from '@snyk/cli-interface'; 5 | import { getMetaData, inspectInstalledDeps } from './inspect-implementation'; 6 | import { getPoetryDependencies } from './poetry'; 7 | import { FILENAMES } from '../types'; 8 | 9 | export interface PythonInspectOptions { 10 | command?: string; // `python` command override 11 | allowMissing?: boolean; // Allow skipping packages that are not found in the environment. 12 | args?: string[]; 13 | projectName?: string; // Allow providing a project name for the root node and package 14 | allowEmpty?: boolean; // Allow manifest without dependencies (mostly for SCM) 15 | } 16 | 17 | type Options = api.SingleSubprojectInspectOptions & PythonInspectOptions; 18 | 19 | // Given a path to a manifest file and assuming that all the packages (transitively required by the 20 | // manifest) were installed (e.g. using `pip install`), produce a tree of dependencies. 21 | export async function getDependencies( 22 | root: string, 23 | targetFile: string, 24 | options?: Options 25 | ): Promise { 26 | if (!options) { 27 | options = {}; 28 | } 29 | let command = options.command || 'python'; 30 | const includeDevDeps = !!(options.dev || false); 31 | 32 | // handle poetry projects by parsing manifest & lockfile and return a dep-graph 33 | if (path.basename(targetFile) === FILENAMES.poetry.lockfile) { 34 | return getPoetryDependencies(command, root, targetFile, includeDevDeps); 35 | } 36 | 37 | let baseargs: string[] = []; 38 | if (path.basename(targetFile) === FILENAMES.pipenv.manifest) { 39 | // Check that pipenv is available by running it. 40 | const pipenvCheckProc = subProcess.executeSync('pipenv', ['--version']); 41 | if (pipenvCheckProc.status !== 0) { 42 | throw new Error( 43 | 'Failed to run `pipenv`; please make sure it is installed.' 44 | ); 45 | } 46 | command = 'pipenv'; 47 | baseargs = ['run', 'python']; 48 | } 49 | 50 | const [plugin, dependencyGraph] = await Promise.all([ 51 | getMetaData(command, baseargs, root, targetFile), 52 | inspectInstalledDeps( 53 | command, 54 | baseargs, 55 | root, 56 | targetFile, 57 | options.allowMissing || false, 58 | includeDevDeps, 59 | options.allowEmpty, 60 | options.args, 61 | options.projectName 62 | ), 63 | ]); 64 | return { plugin, dependencyGraph }; 65 | } 66 | -------------------------------------------------------------------------------- /lib/dependencies/poetry.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { SinglePackageResult } from '@snyk/cli-interface/legacy/plugin'; 4 | import * as poetry from 'snyk-poetry-lockfile-parser'; 5 | import { getMetaData } from './inspect-implementation'; 6 | import { FILENAMES } from '../types'; 7 | 8 | export async function getPoetryDependencies( 9 | command: string, 10 | root: string, 11 | targetFile: string, 12 | includeDevDeps = false 13 | ): Promise { 14 | const lockfilePath = path.join(root, targetFile); 15 | const baseDir = path.dirname(lockfilePath); 16 | const manifestPath = path.join(baseDir, FILENAMES.poetry.manifest); 17 | const manifestExists = fs.existsSync(manifestPath); 18 | 19 | if (!manifestExists) { 20 | throw new Error('Cannot find manifest file ' + manifestPath); 21 | } 22 | const lockfileExists = fs.existsSync(lockfilePath); 23 | if (!lockfileExists) { 24 | throw new Error('Cannot find lockfile ' + lockfilePath); 25 | } 26 | 27 | try { 28 | const manifestContents = fs.readFileSync(manifestPath, 'utf-8'); 29 | const lockfileContents = fs.readFileSync(lockfilePath, 'utf-8'); 30 | const dependencyGraph = poetry.buildDepGraph( 31 | manifestContents, 32 | lockfileContents, 33 | includeDevDeps 34 | ); 35 | const plugin = await getMetaData(command, [], root, targetFile); 36 | return { 37 | plugin, 38 | package: null, 39 | dependencyGraph, 40 | }; 41 | } catch (error) { 42 | throw new Error( 43 | 'Error processing poetry project. ' + (error.message || error) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/dependencies/sub-process.ts: -------------------------------------------------------------------------------- 1 | import { spawn, SpawnOptions, spawnSync } from 'child_process'; 2 | import { quoteAll } from 'shescape/stateless'; 3 | 4 | interface ProcessOptions { 5 | cwd?: string; 6 | env?: { [name: string]: string }; 7 | } 8 | 9 | function makeSpawnOptions(options?: ProcessOptions) { 10 | const spawnOptions: SpawnOptions = { 11 | shell: true, 12 | env: { ...process.env }, 13 | }; 14 | if (options && options.cwd) { 15 | spawnOptions.cwd = options.cwd; 16 | } 17 | if (options && options.env) { 18 | spawnOptions.env = { ...options.env }; 19 | } 20 | 21 | // Before spawning an external process, we look if we need to restore the system proxy configuration, 22 | // which overides the cli internal proxy configuration. 23 | if (process.env.SNYK_SYSTEM_HTTP_PROXY !== undefined) { 24 | spawnOptions.env.HTTP_PROXY = process.env.SNYK_SYSTEM_HTTP_PROXY; 25 | } 26 | if (process.env.SNYK_SYSTEM_HTTPS_PROXY !== undefined) { 27 | spawnOptions.env.HTTPS_PROXY = process.env.SNYK_SYSTEM_HTTPS_PROXY; 28 | } 29 | if (process.env.SNYK_SYSTEM_NO_PROXY !== undefined) { 30 | spawnOptions.env.NO_PROXY = process.env.SNYK_SYSTEM_NO_PROXY; 31 | } 32 | 33 | return spawnOptions; 34 | } 35 | 36 | export function execute( 37 | command: string, 38 | args: string[], 39 | options?: ProcessOptions 40 | ): Promise { 41 | const spawnOptions = makeSpawnOptions(options); 42 | args = quoteAll(args, { flagProtection: false }); 43 | return new Promise((resolve, reject) => { 44 | let stdout = ''; 45 | let stderr = ''; 46 | 47 | const proc = spawn(command, args, spawnOptions); 48 | proc.stdout.on('data', (data) => { 49 | stdout = stdout + data; 50 | }); 51 | proc.stderr.on('data', (data) => { 52 | stderr = stderr + data; 53 | }); 54 | 55 | proc.on('close', (code) => { 56 | if (code !== 0) { 57 | return reject(stdout || stderr); 58 | } 59 | resolve(stdout || stderr); 60 | }); 61 | }); 62 | } 63 | 64 | export function executeSync( 65 | command: string, 66 | args: string[], 67 | options?: ProcessOptions 68 | ) { 69 | const spawnOptions = makeSpawnOptions(options); 70 | args = quoteAll(args, { flagProtection: false }); 71 | 72 | return spawnSync(command, args, spawnOptions); 73 | } 74 | -------------------------------------------------------------------------------- /lib/errors.ts: -------------------------------------------------------------------------------- 1 | export enum PythonPluginErrorNames { 2 | EMPTY_MANIFEST_ERROR = 'EMPTY_MANIFEST_ERROR', 3 | REQUIRED_PACKAGES_MISSING_ERROR = 'REQUIRED_PACKAGES_MISSING_ERROR', 4 | UNPARSABLE_REQUIREMENT_ERROR = 'UNPARSABLE_REQUIREMENT_ERROR', 5 | FAILED_TO_WRITE_TEMP_FILES = 'FAILED_TO_WRITE_TEMP_FILES', 6 | } 7 | 8 | export class EmptyManifestError extends Error { 9 | constructor(message: string) { 10 | super(message); 11 | this.name = PythonPluginErrorNames.EMPTY_MANIFEST_ERROR; 12 | } 13 | } 14 | 15 | export class RequiredPackagesMissingError extends Error { 16 | constructor(message: string) { 17 | super(message); 18 | this.name = PythonPluginErrorNames.REQUIRED_PACKAGES_MISSING_ERROR; 19 | } 20 | } 21 | 22 | export class UnparsableRequirementError extends Error { 23 | constructor(message: string) { 24 | super(message); 25 | this.name = PythonPluginErrorNames.UNPARSABLE_REQUIREMENT_ERROR; 26 | } 27 | } 28 | 29 | export class FailedToWriteTempFiles extends Error { 30 | constructor(message: string) { 31 | super(message); 32 | this.name = PythonPluginErrorNames.FAILED_TO_WRITE_TEMP_FILES; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getDependencies as inspect, 3 | PythonInspectOptions, 4 | } from './dependencies'; 5 | 6 | export { 7 | EmptyManifestError, 8 | RequiredPackagesMissingError, 9 | PythonPluginErrorNames, 10 | } from './errors'; 11 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | export interface DependencyUpdates { 2 | [from: string]: { 3 | upgradeTo: string; 4 | }; 5 | } 6 | 7 | export interface ManifestFiles { 8 | // Typically these are requirements.txt and Pipfile; 9 | // the plugin supports paths with subdirectories 10 | [name: string]: string; // name-to-content 11 | } 12 | 13 | export type PackageManagers = 'pip' | 'setuptools' | 'pipenv' | 'poetry'; 14 | 15 | export const FILENAMES: { 16 | [key in PackageManagers]: { manifest: string; lockfile?: string }; 17 | } = { 18 | pip: { 19 | manifest: 'requirements.txt', 20 | }, 21 | setuptools: { 22 | manifest: 'setup.py', 23 | }, 24 | pipenv: { 25 | manifest: 'Pipfile', 26 | lockfile: 'Pipfile.lock', 27 | }, 28 | poetry: { 29 | manifest: 'pyproject.toml', 30 | lockfile: 'poetry.lock', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snyk-python-plugin", 3 | "description": "Snyk CLI Python plugin", 4 | "homepage": "https://github.com/snyk/snyk-python-plugin", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/snyk/snyk-python-plugin" 8 | }, 9 | "main": "dist/index.js", 10 | "scripts": { 11 | "build": "tsc", 12 | "watch": "tsc -w", 13 | "build-tests": "tsc -p tsconfig-test.json", 14 | "format:check": "prettier --check '{lib,test}/**/*.{js,ts}'", 15 | "format": "prettier --write '{lib,test}/**/*.{js,ts}'", 16 | "prepare": "npm run build", 17 | "test": "npm run test:jest", 18 | "test:jest": "jest", 19 | "test:pysrc2": "cd pysrc; python -m unittest test_pip_resolve_py2 test_pipfile", 20 | "test:pysrc3": "cd pysrc; python -m unittest test_pip_resolve_py3 test_pipfile", 21 | "test:pysrc3_12": "cd pysrc; python -m unittest test_pip_resolve_py3_12 test_pipfile", 22 | "lint": "npm run build-tests && npm run format:check && eslint --cache '{lib,test}/**/*.{js,ts}'" 23 | }, 24 | "author": "snyk.io", 25 | "license": "Apache-2.0", 26 | "dependencies": { 27 | "@snyk/cli-interface": "^2.11.2", 28 | "@snyk/dep-graph": "^1.28.1", 29 | "shescape": "2.1.4", 30 | "snyk-poetry-lockfile-parser": "^1.9.0", 31 | "tmp": "0.2.3" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^28.1.3", 35 | "@types/node": "^20", 36 | "@types/tmp": "^0.1.0", 37 | "@typescript-eslint/eslint-plugin": "^4.33.0", 38 | "@typescript-eslint/parser": "^4.33.0", 39 | "cross-env": "^5.2.0", 40 | "eslint": "^7.32.0", 41 | "eslint-config-prettier": "^8.3.0", 42 | "jest": "^28.1.3", 43 | "jest-diff": "^25.5.0", 44 | "jest-junit": "^10.0.0", 45 | "prettier": "^2.7.1", 46 | "sinon": "^2.3.2", 47 | "ts-jest": "^28.0.8", 48 | "ts-node": "^8.10.2", 49 | "typescript": "^5.8.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pysrc/README.md: -------------------------------------------------------------------------------- 1 | # pysrc 2 | 3 | This is the Python part of the snyk-python-plugin. 4 | 5 | Given a fully installed Python package with its dependencies (using a virtual environment), 6 | it analyzes and returns the dependency tree. 7 | 8 | The entry point is `main` in `pip_resolve.py`. 9 | 10 | ## Implementation outline 11 | 12 | 1. take pkg_resources.working_set (a list of all packages available in the current environment) 13 | 2. convert it to a tree 14 | 3. parse the manifest (requirements.txt/Pipfile) to find the top-level deps 15 | 4. select the parts of the tree that start from TLDs found in previous step 16 | 5. determine actual installed versions for the packages in the tree 17 | 6. convert that tree in DepTree format 18 | 19 | The parts 1 and 5 require access to the Python environment and thus have to be implemented in Python. 20 | The part 3, for requirements.txt, leverages the existing parsing library (pip). 21 | -------------------------------------------------------------------------------- /pysrc/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections import namedtuple 3 | 4 | 5 | DepsManagerItem = namedtuple( 6 | 'DepsManagerItem', ['package_manager', 'file'] 7 | ) 8 | 9 | 10 | # TODO: When support for Python 2.7 - 3.3 will be removed, use Enums instead 11 | # of namedtuple, this is just a workaround to support Python2.7 syntax. 12 | class DepsManager: 13 | PIP = DepsManagerItem(package_manager="pip", file="requirements.txt") 14 | PIPENV = DepsManagerItem(package_manager="pipenv", file="Pipfile") 15 | SETUPTOOLS = DepsManagerItem(package_manager="setuptools", file="setup.py") 16 | 17 | @classmethod 18 | def discover(cls, requirements_file_path): 19 | """Establishes the dependency manager based on the used files""" 20 | if os.path.basename(requirements_file_path) == 'Pipfile': 21 | return cls.PIPENV 22 | if os.path.basename(requirements_file_path) == 'setup.py': 23 | return cls.SETUPTOOLS 24 | 25 | return cls.PIP 26 | 27 | DEFAULT_OPTIONS = { 28 | "allow_missing":False, 29 | "dev_deps":False, 30 | "only_provenance":False, 31 | "allow_empty":False 32 | } -------------------------------------------------------------------------------- /pysrc/distPackage.py: -------------------------------------------------------------------------------- 1 | from package import Package 2 | from reqPackage import ReqPackage 3 | 4 | 5 | class DistPackage(Package): 6 | """Wrapper class for pkg_resources.Distribution instances 7 | :param obj: pkg_resources.Distribution to wrap over 8 | :param req: optional ReqPackage object to associate this 9 | DistPackage with. This is useful for displaying the 10 | tree in reverse 11 | """ 12 | 13 | def __init__(self, obj, req=None): 14 | super(DistPackage, self).__init__(obj) 15 | self.version_spec = None 16 | self.req = req 17 | 18 | def as_requirement(self): 19 | """Return a ReqPackage representation of this DistPackage""" 20 | return ReqPackage(self._obj.as_requirement(), dist=self) 21 | 22 | def as_required_by(self, req): 23 | """Return a DistPackage instance associated to a requirement 24 | This association is necessary for displaying the tree in 25 | reverse. 26 | :param ReqPackage req: the requirement to associate with 27 | :returns: DistPackage instance 28 | """ 29 | return self.__class__(self._obj, req) 30 | 31 | def as_dict(self): 32 | return {'key': self.key, 33 | 'package_name': self.project_name, 34 | 'installed_version': self.version} 35 | -------------------------------------------------------------------------------- /pysrc/package.py: -------------------------------------------------------------------------------- 1 | 2 | class Package(object): 3 | """Abstract class for wrappers around objects that pip returns. 4 | This class needs to be subclassed with implementations for 5 | `render_as_root` and `render_as_branch` methods. 6 | """ 7 | 8 | def __init__(self, obj): 9 | self._obj = obj 10 | self.project_name = obj.project_name 11 | self.key = obj.key 12 | 13 | def render_as_root(self, frozen): 14 | return NotImplementedError 15 | 16 | def render_as_branch(self, frozen): 17 | return NotImplementedError 18 | 19 | def render(self, parent=None, frozen=False): 20 | if not parent: 21 | return self.render_as_root(frozen) 22 | else: 23 | return self.render_as_branch(frozen) 24 | 25 | def __getattr__(self, key): 26 | return getattr(self._obj, key) 27 | 28 | def __repr__(self): 29 | return '<{0}("{1}")>'.format(self.__class__.__name__, self.key) 30 | -------------------------------------------------------------------------------- /pysrc/pipfile.py: -------------------------------------------------------------------------------- 1 | """Simplistic parsing of Pipfile dependency files 2 | 3 | This only extracts a small subset of the information present in a Pipfile, 4 | as needed for the purposes of this library. 5 | """ 6 | import utils 7 | 8 | import pytoml 9 | 10 | 11 | class PipfileRequirement(object): 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | self.editable = False 16 | self.vcs = None 17 | self.vcs_uri = None 18 | self.version = None 19 | self.markers = None 20 | self.provenance = None # a tuple of (file name, line) 21 | self.original_name = None 22 | 23 | def __repr__(self): 24 | return str(self.__dict__()) 25 | 26 | def __dict__(self): 27 | return { 28 | "name": self.name, 29 | "editable": self.editable, 30 | "vcs": self.vcs, 31 | "vcs_uri": self.vcs_uri, 32 | "version": self.version, 33 | "markers": self.markers, 34 | "provenance": self.provenance, 35 | } 36 | 37 | def __eq__(self, other): 38 | if isinstance(other, PipfileRequirement): 39 | return self.__dict__() == other.__dict__() 40 | return False 41 | 42 | @classmethod 43 | def from_dict(cls, name, requirement_dict, pos_in_toml): 44 | req = cls(name) 45 | 46 | req.original_name = req.name 47 | req.version = parse_req(requirement_dict.get('version')) 48 | req.editable = parse_req(requirement_dict.get('editable', False)) 49 | for vcs in ['git', 'hg', 'svn', 'bzr']: 50 | if vcs in requirement_dict: 51 | req.vcs = vcs 52 | req.vcs_uri = requirement_dict[vcs] 53 | break 54 | req.markers = parse_req(requirement_dict.get('markers')) 55 | # proper file name to be injected into provenance by the calling code 56 | req.provenance = ('Pipfile', pos_in_toml[0], pos_in_toml[0]) 57 | return req 58 | 59 | ''' 60 | The toml parser returns each requirement as a tuple 61 | of the value and ending position, for multiple requirements 62 | e.g. 63 | { 64 | 'version': ('*', (9, 23)), 65 | 'markers': ("sys_platform == 'linux' ; python_version != '3.4'", (8, 36)) 66 | } for entry waitress = {version = "*", markers="sys_platform == 'linux' ; python_version != '3.4'"} 67 | This functions returns the value without the position for one such instance 68 | e.g. parse_req(("sys_platform == 'linux' ; python_version != '3.4'", (8, 36))) returns "sys_platform == 'linux' ; python_version != '3.4'" 69 | ''' 70 | def parse_req(pipfile_req): 71 | if type(pipfile_req) is tuple: 72 | return pipfile_req[0] 73 | else: 74 | return pipfile_req 75 | 76 | def val_with_pos(kind, text, value, pos): 77 | return (value, pos) 78 | 79 | def parse(file_contents): 80 | data = pytoml.loads(file_contents, translate=val_with_pos) 81 | 82 | sections = ['packages', 'dev-packages'] 83 | res = dict.fromkeys(sections) 84 | for section in sections: 85 | if section not in data: 86 | continue 87 | 88 | section_data = data[section] 89 | 90 | res[section] = [ 91 | PipfileRequirement.from_dict( 92 | name, 93 | value if not utils.is_string(value) else {'version': value}, 94 | pos, 95 | ) 96 | for name, (value, pos) in sorted(section_data.items()) 97 | ] 98 | 99 | return res 100 | -------------------------------------------------------------------------------- /pysrc/pkg_resources.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 0): 4 | if sys.version_info.minor >= 12: 5 | from pkg_resources_py3_12 import * 6 | else: 7 | from pkg_resources_py3 import * 8 | else: 9 | from pkg_resources_py2 import * 10 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Jason R Coombs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snyk/snyk-python-plugin/1dbb91148981c9c34288f8c0f93616b134c246b1/pysrc/pkg_resources_py2/_vendor/__init__.py -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/__about__.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | __all__ = [ 7 | "__title__", "__summary__", "__uri__", "__version__", "__author__", 8 | "__email__", "__license__", "__copyright__", 9 | ] 10 | 11 | __title__ = "packaging" 12 | __summary__ = "Core utilities for Python packages" 13 | __uri__ = "https://github.com/pypa/packaging" 14 | 15 | __version__ = "16.8" 16 | 17 | __author__ = "Donald Stufft and individual contributors" 18 | __email__ = "donald@stufft.io" 19 | 20 | __license__ = "BSD or Apache License, Version 2.0" 21 | __copyright__ = "Copyright 2014-2016 %s" % __author__ 22 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | from .__about__ import ( 7 | __author__, __copyright__, __email__, __license__, __summary__, __title__, 8 | __uri__, __version__ 9 | ) 10 | 11 | __all__ = [ 12 | "__title__", "__summary__", "__uri__", "__version__", "__author__", 13 | "__email__", "__license__", "__copyright__", 14 | ] 15 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/_compat.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import sys 7 | 8 | 9 | PY2 = sys.version_info[0] == 2 10 | PY3 = sys.version_info[0] == 3 11 | 12 | # flake8: noqa 13 | 14 | if PY3: 15 | string_types = str, 16 | else: 17 | string_types = basestring, 18 | 19 | 20 | def with_metaclass(meta, *bases): 21 | """ 22 | Create a base class with a metaclass. 23 | """ 24 | # This requires a bit of explanation: the basic idea is to make a dummy 25 | # metaclass for one level of class instantiation that replaces itself with 26 | # the actual metaclass. 27 | class metaclass(meta): 28 | def __new__(cls, name, this_bases, d): 29 | return meta(name, bases, d) 30 | return type.__new__(metaclass, 'temporary_class', (), {}) 31 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/_structures.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | 7 | class Infinity(object): 8 | 9 | def __repr__(self): 10 | return "Infinity" 11 | 12 | def __hash__(self): 13 | return hash(repr(self)) 14 | 15 | def __lt__(self, other): 16 | return False 17 | 18 | def __le__(self, other): 19 | return False 20 | 21 | def __eq__(self, other): 22 | return isinstance(other, self.__class__) 23 | 24 | def __ne__(self, other): 25 | return not isinstance(other, self.__class__) 26 | 27 | def __gt__(self, other): 28 | return True 29 | 30 | def __ge__(self, other): 31 | return True 32 | 33 | def __neg__(self): 34 | return NegativeInfinity 35 | 36 | Infinity = Infinity() 37 | 38 | 39 | class NegativeInfinity(object): 40 | 41 | def __repr__(self): 42 | return "-Infinity" 43 | 44 | def __hash__(self): 45 | return hash(repr(self)) 46 | 47 | def __lt__(self, other): 48 | return True 49 | 50 | def __le__(self, other): 51 | return True 52 | 53 | def __eq__(self, other): 54 | return isinstance(other, self.__class__) 55 | 56 | def __ne__(self, other): 57 | return not isinstance(other, self.__class__) 58 | 59 | def __gt__(self, other): 60 | return False 61 | 62 | def __ge__(self, other): 63 | return False 64 | 65 | def __neg__(self): 66 | return Infinity 67 | 68 | NegativeInfinity = NegativeInfinity() 69 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/requirements.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import string 7 | import re 8 | 9 | from pkg_resources_py2.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException 10 | from pkg_resources_py2.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine 11 | from pkg_resources_py2.extern.pyparsing import Literal as L # noqa 12 | from pkg_resources_py2.extern.six.moves.urllib import parse as urlparse 13 | 14 | from .markers import MARKER_EXPR, Marker 15 | from .specifiers import LegacySpecifier, Specifier, SpecifierSet 16 | 17 | 18 | class InvalidRequirement(ValueError): 19 | """ 20 | An invalid requirement was found, users should refer to PEP 508. 21 | """ 22 | 23 | 24 | ALPHANUM = Word(string.ascii_letters + string.digits) 25 | 26 | LBRACKET = L("[").suppress() 27 | RBRACKET = L("]").suppress() 28 | LPAREN = L("(").suppress() 29 | RPAREN = L(")").suppress() 30 | COMMA = L(",").suppress() 31 | SEMICOLON = L(";").suppress() 32 | AT = L("@").suppress() 33 | 34 | PUNCTUATION = Word("-_.") 35 | IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) 36 | IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) 37 | 38 | NAME = IDENTIFIER("name") 39 | EXTRA = IDENTIFIER 40 | 41 | URI = Regex(r'[^ ]+')("url") 42 | URL = (AT + URI) 43 | 44 | EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) 45 | EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") 46 | 47 | VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) 48 | VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) 49 | 50 | VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY 51 | VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), 52 | joinString=",", adjacent=False)("_raw_spec") 53 | _VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) 54 | _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') 55 | 56 | VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") 57 | VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) 58 | 59 | MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") 60 | MARKER_EXPR.setParseAction( 61 | lambda s, l, t: Marker(s[t._original_start:t._original_end]) 62 | ) 63 | MARKER_SEPERATOR = SEMICOLON 64 | MARKER = MARKER_SEPERATOR + MARKER_EXPR 65 | 66 | VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) 67 | URL_AND_MARKER = URL + Optional(MARKER) 68 | 69 | NAMED_REQUIREMENT = \ 70 | NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) 71 | 72 | REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd 73 | 74 | 75 | class Requirement(object): 76 | """Parse a requirement. 77 | 78 | Parse a given requirement string into its parts, such as name, specifier, 79 | URL, and extras. Raises InvalidRequirement on a badly-formed requirement 80 | string. 81 | """ 82 | 83 | # TODO: Can we test whether something is contained within a requirement? 84 | # If so how do we do that? Do we need to test against the _name_ of 85 | # the thing as well as the version? What about the markers? 86 | # TODO: Can we normalize the name and extra name? 87 | 88 | def __init__(self, requirement_string): 89 | try: 90 | req = REQUIREMENT.parseString(requirement_string) 91 | except ParseException as e: 92 | raise InvalidRequirement( 93 | "Invalid requirement, parse error at \"{0!r}\"".format( 94 | requirement_string[e.loc:e.loc + 8])) 95 | 96 | self.name = req.name 97 | if req.url: 98 | parsed_url = urlparse.urlparse(req.url) 99 | if not (parsed_url.scheme and parsed_url.netloc) or ( 100 | not parsed_url.scheme and not parsed_url.netloc): 101 | raise InvalidRequirement("Invalid URL given") 102 | self.url = req.url 103 | else: 104 | self.url = None 105 | self.extras = set(req.extras.asList() if req.extras else []) 106 | self.specifier = SpecifierSet(req.specifier) 107 | self.marker = req.marker if req.marker else None 108 | 109 | def __str__(self): 110 | parts = [self.name] 111 | 112 | if self.extras: 113 | parts.append("[{0}]".format(",".join(sorted(self.extras)))) 114 | 115 | if self.specifier: 116 | parts.append(str(self.specifier)) 117 | 118 | if self.url: 119 | parts.append("@ {0}".format(self.url)) 120 | 121 | if self.marker: 122 | parts.append("; {0}".format(self.marker)) 123 | 124 | return "".join(parts) 125 | 126 | def __repr__(self): 127 | return "".format(str(self)) 128 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/_vendor/packaging/utils.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import re 7 | 8 | 9 | _canonicalize_regex = re.compile(r"[-_.]+") 10 | 11 | 12 | def canonicalize_name(name): 13 | # This is taken from PEP 503. 14 | return _canonicalize_regex.sub("-", name).lower() 15 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/extern/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class VendorImporter: 5 | """ 6 | A PEP 302 meta path importer for finding optionally-vendored 7 | or otherwise naturally-installed packages from root_name. 8 | """ 9 | 10 | def __init__(self, root_name, vendored_names=(), vendor_pkg=None): 11 | self.root_name = root_name 12 | self.vendored_names = set(vendored_names) 13 | self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') 14 | 15 | @property 16 | def search_path(self): 17 | """ 18 | Search first the vendor package then as a natural package. 19 | """ 20 | yield self.vendor_pkg + '.' 21 | yield '' 22 | 23 | def find_module(self, fullname, path=None): 24 | """ 25 | Return self when fullname starts with root_name and the 26 | target module is one vendored through this importer. 27 | """ 28 | root, base, target = fullname.partition(self.root_name + '.') 29 | if root: 30 | return 31 | if not any(map(target.startswith, self.vendored_names)): 32 | return 33 | return self 34 | 35 | def load_module(self, fullname): 36 | """ 37 | Iterate over the search path to locate and load fullname. 38 | """ 39 | root, base, target = fullname.partition(self.root_name + '.') 40 | for prefix in self.search_path: 41 | try: 42 | extant = prefix + target 43 | __import__(extant) 44 | mod = sys.modules[extant] 45 | sys.modules[fullname] = mod 46 | # mysterious hack: 47 | # Remove the reference to the extant package/module 48 | # on later Python versions to cause relative imports 49 | # in the vendor package to resolve the same modules 50 | # as those going through this importer. 51 | if prefix and sys.version_info > (3, 3): 52 | del sys.modules[extant] 53 | return mod 54 | except ImportError: 55 | pass 56 | else: 57 | raise ImportError( 58 | "The '{target}' package is required; " 59 | "normally this is bundled with this package so if you get " 60 | "this warning, consult the packager of your " 61 | "distribution.".format(**locals()) 62 | ) 63 | 64 | def install(self): 65 | """ 66 | Install this importer into sys.meta_path if not already present. 67 | """ 68 | if self not in sys.meta_path: 69 | sys.meta_path.append(self) 70 | 71 | 72 | names = 'packaging', 'pyparsing', 'six', 'appdirs' 73 | VendorImporter(__name__, names).install() 74 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py2/py31compat.py: -------------------------------------------------------------------------------- 1 | import os 2 | import errno 3 | import sys 4 | 5 | from .extern import six 6 | 7 | 8 | def _makedirs_31(path, exist_ok=False): 9 | try: 10 | os.makedirs(path) 11 | except OSError as exc: 12 | if not exist_ok or exc.errno != errno.EEXIST: 13 | raise 14 | 15 | 16 | # rely on compatibility behavior until mode considerations 17 | # and exists_ok considerations are disentangled. 18 | # See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663 19 | needs_makedirs = ( 20 | six.PY2 or 21 | (3, 4) <= sys.version_info < (3, 4, 1) 22 | ) 23 | makedirs = _makedirs_31 if needs_makedirs else os.makedirs 24 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Jason R. Coombs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snyk/snyk-python-plugin/1dbb91148981c9c34288f8c0f93616b134c246b1/pysrc/pkg_resources_py3/_vendor/__init__.py -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/__about__.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | __all__ = [ 6 | "__title__", 7 | "__summary__", 8 | "__uri__", 9 | "__version__", 10 | "__author__", 11 | "__email__", 12 | "__license__", 13 | "__copyright__", 14 | ] 15 | 16 | __title__ = "packaging" 17 | __summary__ = "Core utilities for Python packages" 18 | __uri__ = "https://github.com/pypa/packaging" 19 | 20 | __version__ = "21.2" 21 | 22 | __author__ = "Donald Stufft and individual contributors" 23 | __email__ = "donald@stufft.io" 24 | 25 | __license__ = "BSD-2-Clause or Apache-2.0" 26 | __copyright__ = "2014-2019 %s" % __author__ 27 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | from .__about__ import ( 6 | __author__, 7 | __copyright__, 8 | __email__, 9 | __license__, 10 | __summary__, 11 | __title__, 12 | __uri__, 13 | __version__, 14 | ) 15 | 16 | __all__ = [ 17 | "__title__", 18 | "__summary__", 19 | "__uri__", 20 | "__version__", 21 | "__author__", 22 | "__email__", 23 | "__license__", 24 | "__copyright__", 25 | ] 26 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/_musllinux.py: -------------------------------------------------------------------------------- 1 | """PEP 656 support. 2 | 3 | This module implements logic to detect if the currently running Python is 4 | linked against musl, and what musl version is used. 5 | """ 6 | 7 | import contextlib 8 | import functools 9 | import operator 10 | import os 11 | import re 12 | import struct 13 | import subprocess 14 | import sys 15 | from typing import IO, Iterator, NamedTuple, Optional, Tuple 16 | 17 | 18 | def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: 19 | return struct.unpack(fmt, f.read(struct.calcsize(fmt))) 20 | 21 | 22 | def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: 23 | """Detect musl libc location by parsing the Python executable. 24 | 25 | Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca 26 | ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html 27 | """ 28 | f.seek(0) 29 | try: 30 | ident = _read_unpacked(f, "16B") 31 | except struct.error: 32 | return None 33 | if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. 34 | return None 35 | f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. 36 | 37 | try: 38 | # e_fmt: Format for program header. 39 | # p_fmt: Format for section header. 40 | # p_idx: Indexes to find p_type, p_offset, and p_filesz. 41 | e_fmt, p_fmt, p_idx = { 42 | 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. 43 | 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. 44 | }[ident[4]] 45 | except KeyError: 46 | return None 47 | else: 48 | p_get = operator.itemgetter(*p_idx) 49 | 50 | # Find the interpreter section and return its content. 51 | try: 52 | _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) 53 | except struct.error: 54 | return None 55 | for i in range(e_phnum + 1): 56 | f.seek(e_phoff + e_phentsize * i) 57 | try: 58 | p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) 59 | except struct.error: 60 | return None 61 | if p_type != 3: # Not PT_INTERP. 62 | continue 63 | f.seek(p_offset) 64 | interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") 65 | if "musl" not in interpreter: 66 | return None 67 | return interpreter 68 | return None 69 | 70 | 71 | class _MuslVersion(NamedTuple): 72 | major: int 73 | minor: int 74 | 75 | 76 | def _parse_musl_version(output: str) -> Optional[_MuslVersion]: 77 | lines = [n for n in (n.strip() for n in output.splitlines()) if n] 78 | if len(lines) < 2 or lines[0][:4] != "musl": 79 | return None 80 | m = re.match(r"Version (\d+)\.(\d+)", lines[1]) 81 | if not m: 82 | return None 83 | return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) 84 | 85 | 86 | @functools.lru_cache() 87 | def _get_musl_version(executable: str) -> Optional[_MuslVersion]: 88 | """Detect currently-running musl runtime version. 89 | 90 | This is done by checking the specified executable's dynamic linking 91 | information, and invoking the loader to parse its output for a version 92 | string. If the loader is musl, the output would be something like:: 93 | 94 | musl libc (x86_64) 95 | Version 1.2.2 96 | Dynamic Program Loader 97 | """ 98 | with contextlib.ExitStack() as stack: 99 | try: 100 | f = stack.enter_context(open(executable, "rb")) 101 | except IOError: 102 | return None 103 | ld = _parse_ld_musl_from_elf(f) 104 | if not ld: 105 | return None 106 | proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) 107 | return _parse_musl_version(proc.stderr) 108 | 109 | 110 | def platform_tags(arch: str) -> Iterator[str]: 111 | """Generate musllinux tags compatible to the current platform. 112 | 113 | :param arch: Should be the part of platform tag after the ``linux_`` 114 | prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a 115 | prerequisite for the current platform to be musllinux-compatible. 116 | 117 | :returns: An iterator of compatible musllinux tags. 118 | """ 119 | sys_musl = _get_musl_version(sys.executable) 120 | if sys_musl is None: # Python not dynamically linked against musl. 121 | return 122 | for minor in range(sys_musl.minor, -1, -1): 123 | yield f"musllinux_{sys_musl.major}_{minor}_{arch}" 124 | 125 | 126 | if __name__ == "__main__": # pragma: no cover 127 | import sysconfig 128 | 129 | plat = sysconfig.get_platform() 130 | assert plat.startswith("linux-"), "not linux" 131 | 132 | print("plat:", plat) 133 | print("musl:", _get_musl_version(sys.executable)) 134 | print("tags:", end=" ") 135 | for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): 136 | print(t, end="\n ") 137 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/_structures.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | 6 | class InfinityType: 7 | def __repr__(self) -> str: 8 | return "Infinity" 9 | 10 | def __hash__(self) -> int: 11 | return hash(repr(self)) 12 | 13 | def __lt__(self, other: object) -> bool: 14 | return False 15 | 16 | def __le__(self, other: object) -> bool: 17 | return False 18 | 19 | def __eq__(self, other: object) -> bool: 20 | return isinstance(other, self.__class__) 21 | 22 | def __ne__(self, other: object) -> bool: 23 | return not isinstance(other, self.__class__) 24 | 25 | def __gt__(self, other: object) -> bool: 26 | return True 27 | 28 | def __ge__(self, other: object) -> bool: 29 | return True 30 | 31 | def __neg__(self: object) -> "NegativeInfinityType": 32 | return NegativeInfinity 33 | 34 | 35 | Infinity = InfinityType() 36 | 37 | 38 | class NegativeInfinityType: 39 | def __repr__(self) -> str: 40 | return "-Infinity" 41 | 42 | def __hash__(self) -> int: 43 | return hash(repr(self)) 44 | 45 | def __lt__(self, other: object) -> bool: 46 | return True 47 | 48 | def __le__(self, other: object) -> bool: 49 | return True 50 | 51 | def __eq__(self, other: object) -> bool: 52 | return isinstance(other, self.__class__) 53 | 54 | def __ne__(self, other: object) -> bool: 55 | return not isinstance(other, self.__class__) 56 | 57 | def __gt__(self, other: object) -> bool: 58 | return False 59 | 60 | def __ge__(self, other: object) -> bool: 61 | return False 62 | 63 | def __neg__(self: object) -> InfinityType: 64 | return Infinity 65 | 66 | 67 | NegativeInfinity = NegativeInfinityType() 68 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/requirements.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | import re 6 | import string 7 | import urllib.parse 8 | from typing import List, Optional as TOptional, Set 9 | 10 | from pkg_resources_py3.extern.pyparsing import ( # noqa 11 | Combine, 12 | Literal as L, 13 | Optional, 14 | ParseException, 15 | Regex, 16 | Word, 17 | ZeroOrMore, 18 | originalTextFor, 19 | stringEnd, 20 | stringStart, 21 | ) 22 | 23 | from .markers import MARKER_EXPR, Marker 24 | from .specifiers import LegacySpecifier, Specifier, SpecifierSet 25 | 26 | 27 | class InvalidRequirement(ValueError): 28 | """ 29 | An invalid requirement was found, users should refer to PEP 508. 30 | """ 31 | 32 | 33 | ALPHANUM = Word(string.ascii_letters + string.digits) 34 | 35 | LBRACKET = L("[").suppress() 36 | RBRACKET = L("]").suppress() 37 | LPAREN = L("(").suppress() 38 | RPAREN = L(")").suppress() 39 | COMMA = L(",").suppress() 40 | SEMICOLON = L(";").suppress() 41 | AT = L("@").suppress() 42 | 43 | PUNCTUATION = Word("-_.") 44 | IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) 45 | IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) 46 | 47 | NAME = IDENTIFIER("name") 48 | EXTRA = IDENTIFIER 49 | 50 | URI = Regex(r"[^ ]+")("url") 51 | URL = AT + URI 52 | 53 | EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) 54 | EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") 55 | 56 | VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) 57 | VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) 58 | 59 | VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY 60 | VERSION_MANY = Combine( 61 | VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False 62 | )("_raw_spec") 63 | _VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) 64 | _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") 65 | 66 | VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") 67 | VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) 68 | 69 | MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") 70 | MARKER_EXPR.setParseAction( 71 | lambda s, l, t: Marker(s[t._original_start : t._original_end]) 72 | ) 73 | MARKER_SEPARATOR = SEMICOLON 74 | MARKER = MARKER_SEPARATOR + MARKER_EXPR 75 | 76 | VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) 77 | URL_AND_MARKER = URL + Optional(MARKER) 78 | 79 | NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) 80 | 81 | REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd 82 | # pkg_resources_py3.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see 83 | # issue #104 84 | REQUIREMENT.parseString("x[]") 85 | 86 | 87 | class Requirement: 88 | """Parse a requirement. 89 | 90 | Parse a given requirement string into its parts, such as name, specifier, 91 | URL, and extras. Raises InvalidRequirement on a badly-formed requirement 92 | string. 93 | """ 94 | 95 | # TODO: Can we test whether something is contained within a requirement? 96 | # If so how do we do that? Do we need to test against the _name_ of 97 | # the thing as well as the version? What about the markers? 98 | # TODO: Can we normalize the name and extra name? 99 | 100 | def __init__(self, requirement_string: str) -> None: 101 | try: 102 | req = REQUIREMENT.parseString(requirement_string) 103 | except ParseException as e: 104 | raise InvalidRequirement( 105 | f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' 106 | ) 107 | 108 | self.name: str = req.name 109 | if req.url: 110 | parsed_url = urllib.parse.urlparse(req.url) 111 | if parsed_url.scheme == "file": 112 | if urllib.parse.urlunparse(parsed_url) != req.url: 113 | raise InvalidRequirement("Invalid URL given") 114 | elif not (parsed_url.scheme and parsed_url.netloc) or ( 115 | not parsed_url.scheme and not parsed_url.netloc 116 | ): 117 | raise InvalidRequirement(f"Invalid URL: {req.url}") 118 | self.url: TOptional[str] = req.url 119 | else: 120 | self.url = None 121 | self.extras: Set[str] = set(req.extras.asList() if req.extras else []) 122 | self.specifier: SpecifierSet = SpecifierSet(req.specifier) 123 | self.marker: TOptional[Marker] = req.marker if req.marker else None 124 | 125 | def __str__(self) -> str: 126 | parts: List[str] = [self.name] 127 | 128 | if self.extras: 129 | formatted_extras = ",".join(sorted(self.extras)) 130 | parts.append(f"[{formatted_extras}]") 131 | 132 | if self.specifier: 133 | parts.append(str(self.specifier)) 134 | 135 | if self.url: 136 | parts.append(f"@ {self.url}") 137 | if self.marker: 138 | parts.append(" ") 139 | 140 | if self.marker: 141 | parts.append(f"; {self.marker}") 142 | 143 | return "".join(parts) 144 | 145 | def __repr__(self) -> str: 146 | return f"" 147 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/_vendor/packaging/utils.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | import re 6 | from typing import FrozenSet, NewType, Tuple, Union, cast 7 | 8 | from .tags import Tag, parse_tag 9 | from .version import InvalidVersion, Version 10 | 11 | BuildTag = Union[Tuple[()], Tuple[int, str]] 12 | NormalizedName = NewType("NormalizedName", str) 13 | 14 | 15 | class InvalidWheelFilename(ValueError): 16 | """ 17 | An invalid wheel filename was found, users should refer to PEP 427. 18 | """ 19 | 20 | 21 | class InvalidSdistFilename(ValueError): 22 | """ 23 | An invalid sdist filename was found, users should refer to the packaging user guide. 24 | """ 25 | 26 | 27 | _canonicalize_regex = re.compile(r"[-_.]+") 28 | # PEP 427: The build number must start with a digit. 29 | _build_tag_regex = re.compile(r"(\d+)(.*)") 30 | 31 | 32 | def canonicalize_name(name: str) -> NormalizedName: 33 | # This is taken from PEP 503. 34 | value = _canonicalize_regex.sub("-", name).lower() 35 | return cast(NormalizedName, value) 36 | 37 | 38 | def canonicalize_version(version: Union[Version, str]) -> str: 39 | """ 40 | This is very similar to Version.__str__, but has one subtle difference 41 | with the way it handles the release segment. 42 | """ 43 | if isinstance(version, str): 44 | try: 45 | parsed = Version(version) 46 | except InvalidVersion: 47 | # Legacy versions cannot be normalized 48 | return version 49 | else: 50 | parsed = version 51 | 52 | parts = [] 53 | 54 | # Epoch 55 | if parsed.epoch != 0: 56 | parts.append(f"{parsed.epoch}!") 57 | 58 | # Release segment 59 | # NB: This strips trailing '.0's to normalize 60 | parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) 61 | 62 | # Pre-release 63 | if parsed.pre is not None: 64 | parts.append("".join(str(x) for x in parsed.pre)) 65 | 66 | # Post-release 67 | if parsed.post is not None: 68 | parts.append(f".post{parsed.post}") 69 | 70 | # Development release 71 | if parsed.dev is not None: 72 | parts.append(f".dev{parsed.dev}") 73 | 74 | # Local version segment 75 | if parsed.local is not None: 76 | parts.append(f"+{parsed.local}") 77 | 78 | return "".join(parts) 79 | 80 | 81 | def parse_wheel_filename( 82 | filename: str, 83 | ) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: 84 | if not filename.endswith(".whl"): 85 | raise InvalidWheelFilename( 86 | f"Invalid wheel filename (extension must be '.whl'): {filename}" 87 | ) 88 | 89 | filename = filename[:-4] 90 | dashes = filename.count("-") 91 | if dashes not in (4, 5): 92 | raise InvalidWheelFilename( 93 | f"Invalid wheel filename (wrong number of parts): {filename}" 94 | ) 95 | 96 | parts = filename.split("-", dashes - 2) 97 | name_part = parts[0] 98 | # See PEP 427 for the rules on escaping the project name 99 | if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: 100 | raise InvalidWheelFilename(f"Invalid project name: {filename}") 101 | name = canonicalize_name(name_part) 102 | version = Version(parts[1]) 103 | if dashes == 5: 104 | build_part = parts[2] 105 | build_match = _build_tag_regex.match(build_part) 106 | if build_match is None: 107 | raise InvalidWheelFilename( 108 | f"Invalid build number: {build_part} in '{filename}'" 109 | ) 110 | build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) 111 | else: 112 | build = () 113 | tags = parse_tag(parts[-1]) 114 | return (name, version, build, tags) 115 | 116 | 117 | def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: 118 | if filename.endswith(".tar.gz"): 119 | file_stem = filename[: -len(".tar.gz")] 120 | elif filename.endswith(".zip"): 121 | file_stem = filename[: -len(".zip")] 122 | else: 123 | raise InvalidSdistFilename( 124 | f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" 125 | f" {filename}" 126 | ) 127 | 128 | # We are requiring a PEP 440 version, which cannot contain dashes, 129 | # so we split on the last dash. 130 | name_part, sep, version_part = file_stem.rpartition("-") 131 | if not sep: 132 | raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") 133 | 134 | name = canonicalize_name(name_part) 135 | version = Version(version_part) 136 | return (name, version) 137 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3/extern/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | import sys 3 | 4 | 5 | class VendorImporter: 6 | """ 7 | A PEP 302 meta path importer for finding optionally-vendored 8 | or otherwise naturally-installed packages from root_name. 9 | """ 10 | 11 | def __init__(self, root_name, vendored_names=(), vendor_pkg=None): 12 | self.root_name = root_name 13 | self.vendored_names = set(vendored_names) 14 | self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') 15 | 16 | @property 17 | def search_path(self): 18 | """ 19 | Search first the vendor package then as a natural package. 20 | """ 21 | yield self.vendor_pkg + '.' 22 | yield '' 23 | 24 | def _module_matches_namespace(self, fullname): 25 | """Figure out if the target module is vendored.""" 26 | root, base, target = fullname.partition(self.root_name + '.') 27 | return not root and any(map(target.startswith, self.vendored_names)) 28 | 29 | def load_module(self, fullname): 30 | """ 31 | Iterate over the search path to locate and load fullname. 32 | """ 33 | root, base, target = fullname.partition(self.root_name + '.') 34 | for prefix in self.search_path: 35 | try: 36 | extant = prefix + target 37 | __import__(extant) 38 | mod = sys.modules[extant] 39 | sys.modules[fullname] = mod 40 | return mod 41 | except ImportError: 42 | pass 43 | else: 44 | raise ImportError( 45 | "The '{target}' package is required; " 46 | "normally this is bundled with this package so if you get " 47 | "this warning, consult the packager of your " 48 | "distribution.".format(**locals()) 49 | ) 50 | 51 | def create_module(self, spec): 52 | return self.load_module(spec.name) 53 | 54 | def exec_module(self, module): 55 | pass 56 | 57 | def find_spec(self, fullname, path=None, target=None): 58 | """Return a module spec for vendored names.""" 59 | return ( 60 | importlib.util.spec_from_loader(fullname, self) 61 | if self._module_matches_namespace(fullname) else None 62 | ) 63 | 64 | def install(self): 65 | """ 66 | Install this importer into sys.meta_path if not already present. 67 | """ 68 | if self not in sys.meta_path: 69 | sys.meta_path.append(self) 70 | 71 | 72 | names = 'packaging', 'pyparsing', 'appdirs' 73 | VendorImporter(__name__, names).install() 74 | -------------------------------------------------------------------------------- /pysrc/pkg_resources_py3_12/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Jason R. Coombs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /pysrc/pytoml/LICENSE: -------------------------------------------------------------------------------- 1 | No-notice MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 16 | THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /pysrc/pytoml/README.txt: -------------------------------------------------------------------------------- 1 | This is pytoml v0.1.16, taken from the avakar/pytoml GitHub repo. 2 | 3 | See: https://github.com/avakar/pytoml/releases/tag/v0.1.16 4 | 5 | It is bundled out of necessity due to constraints of the 6 | Snyk CLI plugin architecture. 7 | 8 | Also, it contains a Snyk-specific patch: the user-configurable "translate" 9 | function now also takes a fourth argument, "pos". 10 | -------------------------------------------------------------------------------- /pysrc/pytoml/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import TomlError 2 | from .parser import load, loads 3 | from .writer import dump, dumps 4 | -------------------------------------------------------------------------------- /pysrc/pytoml/core.py: -------------------------------------------------------------------------------- 1 | class TomlError(RuntimeError): 2 | def __init__(self, message, line, col, filename): 3 | RuntimeError.__init__(self, message, line, col, filename) 4 | self.message = message 5 | self.line = line 6 | self.col = col 7 | self.filename = filename 8 | 9 | def __str__(self): 10 | return '{}({}, {}): {}'.format(self.filename, self.line, self.col, self.message) 11 | 12 | def __repr__(self): 13 | return 'TomlError({!r}, {!r}, {!r}, {!r})'.format(self.message, self.line, self.col, self.filename) 14 | -------------------------------------------------------------------------------- /pysrc/pytoml/writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import io, datetime, math, sys 3 | 4 | if sys.version_info[0] == 3: 5 | long = int 6 | unicode = str 7 | 8 | 9 | def dumps(obj, sort_keys=False): 10 | fout = io.StringIO() 11 | dump(obj, fout, sort_keys=sort_keys) 12 | return fout.getvalue() 13 | 14 | 15 | _escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'} 16 | 17 | 18 | def _escape_string(s): 19 | res = [] 20 | start = 0 21 | 22 | def flush(): 23 | if start != i: 24 | res.append(s[start:i]) 25 | return i + 1 26 | 27 | i = 0 28 | while i < len(s): 29 | c = s[i] 30 | if c in '"\\\n\r\t\b\f': 31 | start = flush() 32 | res.append('\\' + _escapes[c]) 33 | elif ord(c) < 0x20: 34 | start = flush() 35 | res.append('\\u%04x' % ord(c)) 36 | i += 1 37 | 38 | flush() 39 | return '"' + ''.join(res) + '"' 40 | 41 | 42 | def _escape_id(s): 43 | if any(not c.isalnum() and c not in '-_' for c in s): 44 | return _escape_string(s) 45 | return s 46 | 47 | 48 | def _format_list(v): 49 | return '[{0}]'.format(', '.join(_format_value(obj) for obj in v)) 50 | 51 | # Formula from: 52 | # https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds 53 | # Once support for py26 is dropped, this can be replaced by td.total_seconds() 54 | def _total_seconds(td): 55 | return ((td.microseconds 56 | + (td.seconds + td.days * 24 * 3600) * 10**6) / 10.0**6) 57 | 58 | def _format_value(v): 59 | if isinstance(v, bool): 60 | return 'true' if v else 'false' 61 | if isinstance(v, int) or isinstance(v, long): 62 | return unicode(v) 63 | if isinstance(v, float): 64 | if math.isnan(v) or math.isinf(v): 65 | raise ValueError("{0} is not a valid TOML value".format(v)) 66 | else: 67 | return repr(v) 68 | elif isinstance(v, unicode) or isinstance(v, bytes): 69 | return _escape_string(v) 70 | elif isinstance(v, datetime.datetime): 71 | offs = v.utcoffset() 72 | offs = _total_seconds(offs) // 60 if offs is not None else 0 73 | 74 | if offs == 0: 75 | suffix = 'Z' 76 | else: 77 | if offs > 0: 78 | suffix = '+' 79 | else: 80 | suffix = '-' 81 | offs = -offs 82 | suffix = '{0}{1:.02}{2:.02}'.format(suffix, offs // 60, offs % 60) 83 | 84 | if v.microsecond: 85 | return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix 86 | else: 87 | return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix 88 | elif isinstance(v, list): 89 | return _format_list(v) 90 | else: 91 | raise RuntimeError(v) 92 | 93 | 94 | def dump(obj, fout, sort_keys=False): 95 | tables = [((), obj, False)] 96 | 97 | while tables: 98 | name, table, is_array = tables.pop() 99 | if name: 100 | section_name = '.'.join(_escape_id(c) for c in name) 101 | if is_array: 102 | fout.write('[[{0}]]\n'.format(section_name)) 103 | else: 104 | fout.write('[{0}]\n'.format(section_name)) 105 | 106 | table_keys = sorted(table.keys()) if sort_keys else table.keys() 107 | new_tables = [] 108 | has_kv = False 109 | for k in table_keys: 110 | v = table[k] 111 | if isinstance(v, dict): 112 | new_tables.append((name + (k,), v, False)) 113 | elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v): 114 | new_tables.extend((name + (k,), d, True) for d in v) 115 | elif v is None: 116 | # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344 117 | fout.write( 118 | '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k))) 119 | has_kv = True 120 | else: 121 | fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v))) 122 | has_kv = True 123 | 124 | tables.extend(reversed(new_tables)) 125 | 126 | if (name or has_kv) and tables: 127 | fout.write('\n') 128 | -------------------------------------------------------------------------------- /pysrc/reqPackage.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import utils 3 | from package import Package 4 | 5 | 6 | class ReqPackage(Package): 7 | """Wrapper class for Requirements instance 8 | :param obj: The `Requirements` instance to wrap over 9 | :param dist: optional `pkg_resources.Distribution` instance for 10 | this requirement 11 | """ 12 | 13 | UNKNOWN_VERSION = '?' 14 | 15 | def __init__(self, obj, dist=None): 16 | super(ReqPackage, self).__init__(obj) 17 | self.dist = dist 18 | 19 | @property 20 | def version_spec(self): 21 | specs = self._obj.specs 22 | return ','.join([''.join(sp) for sp in specs]) if specs else None 23 | 24 | @property 25 | def installed_version(self): 26 | if not self.dist: 27 | return utils.guess_version(self.key, self.UNKNOWN_VERSION) 28 | return self.dist.version 29 | 30 | def is_conflicting(self): 31 | """If installed version conflicts with required version""" 32 | # unknown installed version is also considered conflicting 33 | if self.installed_version == self.UNKNOWN_VERSION: 34 | return True 35 | ver_spec = (self.version_spec if self.version_spec else '') 36 | req_version_str = '{0}{1}'.format(self.project_name, ver_spec) 37 | req_obj = pkg_resources.Requirement.parse(req_version_str) 38 | return self.installed_version not in req_obj 39 | 40 | def as_dict(self): 41 | return {'key': self.key, 42 | 'package_name': self.project_name, 43 | 'installed_version': self.installed_version, 44 | 'required_version': self.version_spec} 45 | -------------------------------------------------------------------------------- /pysrc/requirements/LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | Requirements Parser is licensed under the BSD license. 5 | 6 | Copyright (c) 2012 - 2013, David Fischer 7 | 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | - Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | - Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY David Fischer ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL David Fischer BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /pysrc/requirements/README.txt: -------------------------------------------------------------------------------- 1 | This code comes from: davidfischer/requirements-parser 2 | 3 | See: https://github.com/davidfischer/requirements-parser 4 | 5 | It is bundled out of necessity due to constraints of the 6 | Snyk CLI plugin architecture. 7 | 8 | PLEASE NOTE: There have been some minor modifications. These are 9 | called out at the top of a modified file. 10 | -------------------------------------------------------------------------------- /pysrc/requirements/__init__.py: -------------------------------------------------------------------------------- 1 | from .parser import parse # noqa 2 | 3 | _MAJOR = 0 4 | _MINOR = 1 5 | _PATCH = 0 6 | 7 | 8 | def version_tuple(): 9 | ''' 10 | Returns a 3-tuple of ints that represent the version 11 | ''' 12 | return (_MAJOR, _MINOR, _PATCH) 13 | 14 | 15 | def version(): 16 | ''' 17 | Returns a string representation of the version 18 | ''' 19 | return '%d.%d.%d' % (version_tuple()) 20 | 21 | 22 | __version__ = version() 23 | -------------------------------------------------------------------------------- /pysrc/requirements/fragment.py: -------------------------------------------------------------------------------- 1 | # MODIFIED - Replace/Update with care 2 | 3 | import re 4 | 5 | # Copied from pip 6 | # https://github.com/pypa/pip/blob/281eb61b09d87765d7c2b92f6982b3fe76ccb0af/pip/index.py#L947 7 | HASH_ALGORITHMS = set(['sha1', 'sha224', 'sha384', 'sha256', 'sha512', 'md5']) 8 | 9 | extras_require_search = re.compile( 10 | r'(?P.+)\[(?P[^\]]+)\]').search 11 | 12 | 13 | def parse_fragment(fragment_string): 14 | """Takes a fragment string nd returns a dict of the components""" 15 | fragment_string = fragment_string.lstrip('#') 16 | 17 | try: 18 | return dict( 19 | key_value_string.split('==')[0].split('=') 20 | for key_value_string in fragment_string.split('&') 21 | ) 22 | except ValueError: 23 | raise ValueError( 24 | 'Invalid fragment string {fragment_string}'.format( 25 | fragment_string=fragment_string 26 | ) 27 | ) 28 | 29 | 30 | def get_hash_info(d): 31 | """Returns the first matching hashlib name and value from a dict""" 32 | for key in d.keys(): 33 | if key.lower() in HASH_ALGORITHMS: 34 | return key, d[key] 35 | 36 | return None, None 37 | 38 | 39 | def parse_extras_require(egg): 40 | if egg is not None: 41 | match = extras_require_search(egg) 42 | if match is not None: 43 | name = match.group('name') 44 | extras = match.group('extras') 45 | return name, [extra.strip() for extra in extras.split(',')] 46 | return egg, [] 47 | -------------------------------------------------------------------------------- /pysrc/requirements/parser.py: -------------------------------------------------------------------------------- 1 | # MODIFIED - Replace/Update with care 2 | 3 | import os 4 | import warnings 5 | import sys 6 | 7 | from .requirement import Requirement 8 | 9 | def parse(req_str_or_file, options={ 10 | "allow_missing":False, 11 | "dev_deps":False, 12 | "only_provenance":False, 13 | "allow_empty":False 14 | }): 15 | """ 16 | Parse a requirements file into a list of Requirements 17 | 18 | See: pip/req.py:parse_requirements() and 19 | https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format 20 | 21 | :param req_str_or_file: a string or file like object containing requirements 22 | :returns: a *generator* of Requirement objects 23 | """ 24 | filename = getattr(req_str_or_file, 'name', None) 25 | reqstr = req_str_or_file 26 | try: 27 | # Python 2.x compatibility 28 | if not isinstance(req_str_or_file, basestring): 29 | reqstr = req_str_or_file.read() 30 | except NameError: 31 | # Python 3.x only 32 | if not isinstance(req_str_or_file, str): 33 | reqstr = req_str_or_file.read() 34 | 35 | # To deal with lines broken via a backslash 36 | initial_zero_based_line_idx = 0 37 | accumulator_line_parts = [] 38 | 39 | for zero_based_line_idx, original_line in enumerate(reqstr.splitlines()): 40 | 41 | if original_line.endswith('\\'): 42 | accumulator_line_parts.append(original_line.rstrip('\\')) 43 | continue 44 | 45 | # Construct a full line from pieces broken by backslashes 46 | accumulator_line_parts.append(original_line) 47 | original_line_idxs = (initial_zero_based_line_idx, zero_based_line_idx) 48 | initial_zero_based_line_idx = zero_based_line_idx + 1 49 | line = ' '.join(accumulator_line_parts) 50 | accumulator_line_parts = [] 51 | 52 | line = line.strip() 53 | 54 | if line == '': 55 | continue 56 | elif not line or line.startswith('#'): 57 | # comments are lines that start with # only 58 | continue 59 | elif line.startswith('--trusted-host'): 60 | # unsupported 61 | continue 62 | elif line.startswith('-r') or line.startswith('--requirement'): 63 | new_filename = line.split()[1] 64 | new_file_path = os.path.join(os.path.dirname(filename or '.'), 65 | new_filename) 66 | with open(new_file_path) as f: 67 | for requirement in parse(f, options): 68 | yield requirement 69 | elif line.startswith('-f') or line.startswith('--find-links') or \ 70 | line.startswith('-i') or line.startswith('--index-url') or \ 71 | line.startswith('--extra-index-url') or \ 72 | line.startswith('--no-index'): 73 | warnings.warn('Private repos not supported. Skipping.') 74 | continue 75 | elif line.startswith('-Z') or line.startswith('--always-unzip'): 76 | warnings.warn('Unused option --always-unzip. Skipping.') 77 | continue 78 | elif line.startswith('-'): 79 | warnings.warn('Line starts with an option (%s). Skipping.' % \ 80 | line.split()[0]) 81 | continue 82 | else: 83 | try: 84 | req = Requirement.parse(line) 85 | except Exception as e: 86 | if options.allow_missing: 87 | warnings.warn("Skipping line (%s).\n Couldn't process: (%s)." %(line.split()[0], e)) 88 | continue 89 | sys.exit('Unparsable requirement line (%s)' %(e)) 90 | req.provenance = ( 91 | filename, 92 | original_line_idxs[0] + 1, 93 | original_line_idxs[1] + 1, 94 | ) 95 | yield req 96 | -------------------------------------------------------------------------------- /pysrc/requirements/vcs.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | VCS = [ 4 | 'git', 5 | 'hg', 6 | 'svn', 7 | 'bzr', 8 | ] 9 | 10 | VCS_SCHEMES = [ 11 | 'git', 12 | 'git+https', 13 | 'git+ssh', 14 | 'git+git', 15 | 'hg+http', 16 | 'hg+https', 17 | 'hg+static-http', 18 | 'hg+ssh', 19 | 'svn', 20 | 'svn+svn', 21 | 'svn+http', 22 | 'svn+https', 23 | 'svn+ssh', 24 | 'bzr+http', 25 | 'bzr+https', 26 | 'bzr+ssh', 27 | 'bzr+sftp', 28 | 'bzr+ftp', 29 | 'bzr+lp', 30 | ] 31 | -------------------------------------------------------------------------------- /pysrc/setup_file.py: -------------------------------------------------------------------------------- 1 | import distutils.core 2 | import re 3 | import setuptools 4 | 5 | def parse(setup_py_content): 6 | """Parse a setup.py file and extract the arguments passed to the setup method""" 7 | # Make the setup method return the arguments that are passed to it 8 | def _save_passed_args(**kwargs): 9 | return kwargs 10 | 11 | distutils.core.setup = _save_passed_args 12 | setuptools.setup = _save_passed_args 13 | setup_py_content = re.sub(r"(setuptools\.)?setup\(", r"passed_arguments = \g<0>", setup_py_content) 14 | 15 | # Fetch the arguments that were passed to the setup.py 16 | exec(setup_py_content, globals()) 17 | return globals()["passed_arguments"] 18 | 19 | 20 | def parse_name_and_version(setup_py_content): 21 | """Extract the name and version from a setup.py file""" 22 | passed_arguments = parse(setup_py_content) 23 | return passed_arguments.get("name"), passed_arguments.get("version") 24 | 25 | 26 | def get_provenance(setup_py_content): 27 | """Provenance for a setup.py file is the index of the line that contains `install_requires`""" 28 | for index, line in enumerate(setup_py_content.splitlines()): 29 | if re.search(r"(packages|install_requires)\s*=", line): 30 | return index + 1 31 | return -1 32 | 33 | 34 | def parse_requirements(setup_py_content): 35 | """Extract the dependencies from a setup.py file""" 36 | passed_arguments = parse(setup_py_content) 37 | requirements = passed_arguments.get("install_requires", passed_arguments.get("packages", [])) 38 | return "\n".join(requirements) 39 | -------------------------------------------------------------------------------- /pysrc/test_pip_resolve_py2.py: -------------------------------------------------------------------------------- 1 | # run with: 2 | # cd pysrc; python3 test_pip_resolve.py; cd .. 3 | 4 | from pip_resolve import satisfies_python_requirement, \ 5 | matches_python_version, \ 6 | matches_environment, \ 7 | canonicalize_package_name 8 | from collections import namedtuple 9 | 10 | import unittest 11 | 12 | try: 13 | from mock import patch 14 | except: 15 | from unittest.mock import patch 16 | 17 | class TestStringMethods(unittest.TestCase): 18 | 19 | def test_canonicalize_package_name(self): 20 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#name 21 | self.assertEqual(canonicalize_package_name("Cool-Stuff"), "cool.stuff") 22 | self.assertEqual(canonicalize_package_name("Cool--.--Stuff"), "cool.stuff") 23 | self.assertEqual(canonicalize_package_name("Cool--__.__--Stuff"), "cool.stuff") 24 | 25 | self.assertEqual(canonicalize_package_name("cool.stuff"), "cool.stuff") 26 | self.assertEqual(canonicalize_package_name("COOL_STUFF"), "cool.stuff") 27 | self.assertEqual(canonicalize_package_name("CoOl__-.-__sTuFF"), "cool.stuff") 28 | 29 | 30 | def test_satisfies_python_requirement(self): 31 | 32 | with patch('pip_resolve.sys') as mock_sys: 33 | mock_sys.version_info = (2, 5) 34 | self.assertTrue(satisfies_python_requirement('>', '2.4')) 35 | 36 | mock_sys.version_info = (2, 3) 37 | self.assertTrue(satisfies_python_requirement('==', '2.3')) 38 | self.assertTrue(satisfies_python_requirement('<=', '2.3')) 39 | self.assertFalse(satisfies_python_requirement('<', '2.3')) 40 | 41 | mock_sys.version_info = (2, 8) 42 | self.assertFalse(satisfies_python_requirement('>', '3.1')) 43 | 44 | mock_sys.version_info = (2, 6) 45 | self.assertTrue(satisfies_python_requirement('==', '2.*')) 46 | 47 | 48 | 49 | def test_matches_python_version(self): 50 | 51 | req = namedtuple('requirement', ['line']) 52 | 53 | with patch('pip_resolve.sys') as mock_sys: 54 | 55 | mock_sys.version_info = (2, 5) 56 | req.line = "futures==3.2.0; python_version == '2.6'" 57 | self.assertFalse(matches_python_version(req)) 58 | 59 | mock_sys.version_info = (2, 6) 60 | req.line = "futures==3.2.0; python_version == '2.6'" 61 | self.assertTrue(matches_python_version(req)) 62 | 63 | mock_sys.version_info = (2, 5) 64 | req.line = "futures==3.2.0; python_version <= '2.6'" 65 | self.assertTrue(matches_python_version(req)) 66 | 67 | mock_sys.version_info = (2, 5) 68 | req.line = 'futures==3.2.0; python_version <= "2.6"' 69 | self.assertTrue(matches_python_version(req)) 70 | 71 | # BUG: python_version is always expected on the left side 72 | # mock_sys.version_info = (2, 5) 73 | # req.line = 'futures==3.2.0; "2.6" >= python_version' 74 | # self.assertTrue(matches_python_version(req)) 75 | 76 | # BUG: Double quotes are supported but allow illegal statements 77 | mock_sys.version_info = (2, 5) 78 | req.line = '''futures==3.2.0; python_version <= '2.6"''' 79 | self.assertTrue(matches_python_version(req)) 80 | 81 | mock_sys.version_info = (2, 6) 82 | req.line = "futures==3.2.0; python_version == '2.6' or python_version == '2.7'" 83 | self.assertTrue(matches_python_version(req)) 84 | 85 | mock_sys.version_info = (2, 7) 86 | req.line = "futures==3.2.0 ; python_version == '2.6' or python_version == '2.7'" 87 | self.assertTrue(matches_python_version(req)) 88 | 89 | mock_sys.version_info = (2, 7) 90 | req.line = "futures==3.2.0 ; python_version == '2.5' or python_version == '2.6'" \ 91 | " or python_version == '2.7'" 92 | self.assertTrue(matches_python_version(req)) 93 | 94 | # BUG: Comments are not supported 95 | #mock_sys.version_info = (2, 7) 96 | #req.line = "futures==3.2.0 ; python_version == '2.6' # or python_version == '2.7'" 97 | #self.assertFalse(matches_python_version(req)) 98 | 99 | # BUG: The 'and' case doesn't really make sesne but should be handled correctly 100 | mock_sys.version_info = (2, 7) 101 | req.line = "futures==3.2.0 ; python_version == '2.6' and python_version == '2.7'" 102 | self.assertTrue(matches_python_version(req)) 103 | 104 | mock_sys.version_info = (2, 6) 105 | req.line = "futures==3.2.0; python_version == '2.6' and sys_platform == 'linux2'" 106 | self.assertTrue(matches_python_version(req)) 107 | 108 | mock_sys.version_info = (2, 7) 109 | req.line = "futures==3.2.0; python_version == '2.6' and sys_platform == 'linux2'" 110 | self.assertFalse(matches_python_version(req)) 111 | 112 | 113 | def test_matches_environment(self): 114 | 115 | req = namedtuple('requirement', ['line']) 116 | 117 | with patch('pip_resolve.sys') as mock_sys: 118 | 119 | mock_sys.platform = "LInux2" 120 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 121 | self.assertTrue(matches_environment(req)) 122 | 123 | # BUG: sys_platform is always expected on the left side 124 | # mock_sys.platform = "win2000" 125 | # req.line = "futures==3.2.0; 'linux2' == sys_platform" 126 | # self.assertFalse(matches_environment(req)) 127 | 128 | mock_sys.platform = "linux2" 129 | req.line = 'futures==3.2.0; sys_platform == "linux2"' 130 | self.assertTrue(matches_environment(req)) 131 | 132 | mock_sys.platform = "win2000" 133 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 134 | self.assertFalse (matches_environment(req)) 135 | 136 | # BUG: Only == operator is supported in the moment 137 | # mock_sys.platform = "linux2" 138 | # req.line = "futures==3.2.0; sys_platform != 'linux2'" 139 | # self.assertTrue(matches_environment(req)) 140 | 141 | # BUG: Expressions containing logical operators are not supported 142 | # mock_sys.platform = "win2000" 143 | # req.line = "futures==3.2.0; python_version == '2.6' and sys_platform == 'linux2'" 144 | # self.assertTrue(matches_environment(req)) 145 | 146 | 147 | 148 | if __name__ == '__main__': 149 | unittest.main() 150 | -------------------------------------------------------------------------------- /pysrc/test_pip_resolve_py3.py: -------------------------------------------------------------------------------- 1 | # run with: 2 | # cd pysrc; python3 test_pip_resolve.py; cd .. 3 | 4 | from pip_resolve import satisfies_python_requirement, \ 5 | matches_environment, \ 6 | canonicalize_package_name 7 | from collections import namedtuple 8 | 9 | import unittest 10 | 11 | try: 12 | from mock import patch 13 | except: 14 | from unittest.mock import patch 15 | 16 | class TestStringMethods(unittest.TestCase): 17 | 18 | def test_canonicalize_package_name(self): 19 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#name 20 | self.assertEqual(canonicalize_package_name("Cool-Stuff"), "cool.stuff") 21 | self.assertEqual(canonicalize_package_name("Cool--.--Stuff"), "cool.stuff") 22 | self.assertEqual(canonicalize_package_name("Cool--__.__--Stuff"), "cool.stuff") 23 | 24 | self.assertEqual(canonicalize_package_name("cool.stuff"), "cool.stuff") 25 | self.assertEqual(canonicalize_package_name("COOL_STUFF"), "cool.stuff") 26 | self.assertEqual(canonicalize_package_name("CoOl__-.-__sTuFF"), "cool.stuff") 27 | 28 | 29 | def test_satisfies_python_requirement(self): 30 | 31 | with patch('pip_resolve.sys') as mock_sys: 32 | mock_sys.version_info = (3, 5) 33 | self.assertTrue(satisfies_python_requirement('>', '3.1')) 34 | 35 | mock_sys.version_info = (3, 6) 36 | self.assertTrue(satisfies_python_requirement('==', '3.*')) 37 | 38 | def test_matches_environment(self): 39 | 40 | req = namedtuple('requirement', ['line']) 41 | 42 | with patch('pip_resolve.sys') as mock_sys: 43 | 44 | mock_sys.platform = "LInux2" 45 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 46 | self.assertTrue(matches_environment(req)) 47 | 48 | # BUG: sys_platform is always expected on the left side 49 | # mock_sys.platform = "win2000" 50 | # req.line = "futures==3.2.0; 'linux2' == sys_platform" 51 | # self.assertFalse(matches_environment(req)) 52 | 53 | mock_sys.platform = "linux2" 54 | req.line = 'futures==3.2.0; sys_platform == "linux2"' 55 | self.assertTrue(matches_environment(req)) 56 | 57 | mock_sys.platform = "win2000" 58 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 59 | self.assertFalse (matches_environment(req)) 60 | 61 | mock_sys.platform = "linux" 62 | req.line = "jinja2==3.1.4 ; sys_platform == 'darwin' or sys_platform == 'linux'" 63 | self.assertTrue(matches_environment(req)) 64 | 65 | mock_sys.platform = "darwin" 66 | req.line = "jinja2==3.1.4 ; sys_platform == 'darwin' or sys_platform == 'linux'" 67 | self.assertTrue(matches_environment(req)) 68 | 69 | # BUG: Only == operator is supported in the moment 70 | # mock_sys.platform = "linux2" 71 | # req.line = "futures==3.2.0; sys_platform != 'linux2'" 72 | # self.assertTrue(matches_environment(req)) 73 | 74 | # BUG: Expressions containing logical operators are not supported 75 | # mock_sys.platform = "win2000" 76 | # req.line = "futures==3.2.0; python_version == '2.6' and sys_platform == 'linux2'" 77 | # self.assertTrue(matches_environment(req)) 78 | 79 | 80 | 81 | if __name__ == '__main__': 82 | unittest.main() 83 | -------------------------------------------------------------------------------- /pysrc/test_pip_resolve_py3_12.py: -------------------------------------------------------------------------------- 1 | # run with: 2 | # cd pysrc; python3 test_pip_resolve.py; cd .. 3 | 4 | from pip_resolve import ( 5 | satisfies_python_requirement, 6 | matches_environment, 7 | canonicalize_package_name 8 | ) 9 | from collections import namedtuple 10 | 11 | import unittest 12 | 13 | from unittest.mock import patch 14 | 15 | class TestStringMethods(unittest.TestCase): 16 | 17 | def test_canonicalize_package_name(self): 18 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#name 19 | self.assertEqual(canonicalize_package_name("Cool-Stuff"), "cool.stuff") 20 | self.assertEqual(canonicalize_package_name("Cool--.--Stuff"), "cool.stuff") 21 | self.assertEqual(canonicalize_package_name("Cool--__.__--Stuff"), "cool.stuff") 22 | 23 | self.assertEqual(canonicalize_package_name("cool.stuff"), "cool.stuff") 24 | self.assertEqual(canonicalize_package_name("COOL_STUFF"), "cool.stuff") 25 | self.assertEqual(canonicalize_package_name("CoOl__-.-__sTuFF"), "cool.stuff") 26 | 27 | 28 | def test_satisfies_python_requirement(self): 29 | 30 | with patch('pip_resolve.sys') as mock_sys: 31 | mock_sys.version_info = (3, 12) 32 | self.assertTrue(satisfies_python_requirement('>', '3.11')) 33 | 34 | mock_sys.version_info = (3, 12) 35 | self.assertTrue(satisfies_python_requirement('==', '3.*')) 36 | 37 | def test_matches_environment(self): 38 | 39 | req = namedtuple('requirement', ['line']) 40 | 41 | with patch('pip_resolve.sys') as mock_sys: 42 | 43 | mock_sys.platform = "LInux2" 44 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 45 | self.assertTrue(matches_environment(req)) 46 | 47 | # BUG: sys_platform is always expected on the left side 48 | # mock_sys.platform = "win2000" 49 | # req.line = "futures==3.2.0; 'linux2' == sys_platform" 50 | # self.assertFalse(matches_environment(req)) 51 | 52 | mock_sys.platform = "linux2" 53 | req.line = 'futures==3.2.0; sys_platform == "linux2"' 54 | self.assertTrue(matches_environment(req)) 55 | 56 | mock_sys.platform = "win2000" 57 | req.line = "futures==3.2.0; sys_platform == 'linux2'" 58 | self.assertFalse (matches_environment(req)) 59 | 60 | # BUG: Only == operator is supported in the moment 61 | # mock_sys.platform = "linux2" 62 | # req.line = "futures==3.2.0; sys_platform != 'linux2'" 63 | # self.assertTrue(matches_environment(req)) 64 | 65 | # BUG: Expressions containing logical operators are not supported 66 | # mock_sys.platform = "win2000" 67 | # req.line = "futures==3.2.0; python_version == '2.6' and sys_platform == 'linux2'" 68 | # self.assertTrue(matches_environment(req)) 69 | 70 | 71 | 72 | if __name__ == '__main__': 73 | unittest.main() 74 | -------------------------------------------------------------------------------- /pysrc/test_pipfile.py: -------------------------------------------------------------------------------- 1 | # run with: 2 | # cd pysrc; python3 test_pipfile.py; cd .. 3 | 4 | from pipfile import PipfileRequirement, parse 5 | from collections import namedtuple 6 | 7 | import unittest 8 | 9 | try: 10 | from mock import patch 11 | except: 12 | from unittest.mock import patch 13 | 14 | class TestPipfileRequirement(unittest.TestCase): 15 | def test_init(self): 16 | req = PipfileRequirement("example") 17 | self.assertEqual(req.name, "example") 18 | self.assertFalse(req.editable) 19 | self.assertIsNone(req.vcs) 20 | self.assertIsNone(req.vcs_uri) 21 | self.assertIsNone(req.version) 22 | self.assertIsNone(req.markers) 23 | self.assertIsNone(req.provenance) 24 | 25 | def test_from_dict(self): 26 | test_cases = [ 27 | { 28 | "input": { 29 | "name": "example", 30 | "requirement_dict": { 31 | "version": '*', 32 | "editable": True, 33 | "git": 'git_uri', 34 | "markers": 'sys_platform == "linux" ; python_version != "3.4"' 35 | }, 36 | "pos_in_toml": (1, 2) 37 | }, 38 | "expected_output": { 39 | "name": "example", 40 | "editable": True, 41 | "vcs": "git", 42 | "vcs_uri": "git_uri", 43 | "version": "*", 44 | "markers": 'sys_platform == "linux" ; python_version != "3.4"', 45 | "provenance": ('Pipfile', 1, 1) 46 | } 47 | }, 48 | { 49 | "input": { 50 | "name": "example2", 51 | "requirement_dict": { 52 | "version": ('*', (9, 23)), 53 | "editable": False, 54 | "markers": ('sys_platform == "linux" ; python_version != "3.4"', (8, 36)) 55 | }, 56 | "pos_in_toml": (1, 2) 57 | }, 58 | "expected_output": { 59 | "name": "example2", 60 | "editable": False, 61 | "vcs": None, 62 | "vcs_uri": None, 63 | "version": "*", 64 | "markers": 'sys_platform == "linux" ; python_version != "3.4"', 65 | "provenance": ('Pipfile', 1, 1) 66 | } 67 | } 68 | ] 69 | for test_case in test_cases: 70 | test_input = test_case["input"] 71 | expected_output = test_case["expected_output"] 72 | req = PipfileRequirement.from_dict(test_input["name"], test_input["requirement_dict"], test_input["pos_in_toml"]) 73 | self.assertEqual(str(req), str(expected_output)) 74 | 75 | def test_parse(self): 76 | test_cases = [ 77 | { 78 | "input": """ 79 | [packages] 80 | requests = "*" 81 | flask = { version = "1.0", markers = "python_version < '3.7'" } 82 | 83 | [dev-packages] 84 | pytest = "*" 85 | """, 86 | "expected_output": { 87 | "packages": [ 88 | { 89 | "name": "flask", 90 | "editable": False, 91 | "vcs": None, 92 | "vcs_uri": None, 93 | "version": "1.0", 94 | "markers": "python_version < \'3.7\'", 95 | "provenance": ('Pipfile', 4, 4) 96 | }, 97 | { 98 | "name": "requests", 99 | "editable": False, 100 | "vcs": None, 101 | "vcs_uri": None, 102 | "version": "*", 103 | "markers": None, 104 | "provenance": ('Pipfile', 3, 3) 105 | } 106 | ], 107 | "dev-packages": [ 108 | { 109 | "name": "pytest", 110 | "editable": False, 111 | "vcs": None, 112 | "vcs_uri": None, 113 | "version": "*", 114 | "markers": None, 115 | "provenance": ('Pipfile', 7, 7) 116 | } 117 | ] 118 | } 119 | }, 120 | { 121 | "input": """ 122 | [packages] 123 | requests = {version = "==2.28.1"} 124 | 125 | [requires] 126 | python_version = "3.7" 127 | """, 128 | "expected_output": { 129 | "packages": [ 130 | { 131 | "name": "requests", 132 | "editable": False, 133 | "vcs": None, 134 | "vcs_uri": None, 135 | "version": "==2.28.1", 136 | "markers": None, 137 | "provenance": ('Pipfile', 3, 3) 138 | } 139 | ], 140 | "dev-packages": None, 141 | } 142 | }, 143 | { 144 | "input": """ 145 | [[source]] 146 | url = "https://pypi.org/simple" 147 | verify_ssl = true 148 | name = "pypi" 149 | 150 | [packages] 151 | "Jinja2" = "*" 152 | 153 | [dev-packages] 154 | 155 | [requires] 156 | """, 157 | "expected_output": { 158 | "packages": [ 159 | { 160 | "name": "Jinja2", 161 | "editable": False, 162 | "vcs": None, 163 | "vcs_uri": None, 164 | "version": "*", 165 | "markers": None, 166 | "provenance": ('Pipfile', 8, 8) 167 | } 168 | ], 169 | "dev-packages": [], 170 | } 171 | } 172 | ] 173 | 174 | for test_case in test_cases: 175 | pipfile_content = test_case["input"] 176 | expected_output = test_case["expected_output"] 177 | 178 | parsed_data = parse(pipfile_content) 179 | 180 | self.assertEqual(str(parsed_data), str(expected_output)) 181 | 182 | 183 | if __name__ == '__main__': 184 | unittest.main() 185 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | setup( 5 | name="pysrc", 6 | packages=["pysrc"], 7 | ) 8 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION 2 | FROM node:${NODE_VERSION} 3 | 4 | ARG PYTHON_VERSION 5 | ARG PY_TEST_CMD 6 | 7 | ARG DEVUSER=node 8 | USER ${DEVUSER} 9 | 10 | SHELL ["/bin/bash", "-c"] 11 | 12 | WORKDIR /home/${DEVUSER} 13 | 14 | ENV PYTHON_VERSION $PYTHON_VERSION 15 | 16 | RUN set -ex \ 17 | && curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash \ 18 | && export PATH="$HOME/.pyenv/bin:$PATH" \ 19 | && pyenv update \ 20 | && pyenv install $PYTHON_VERSION \ 21 | && pyenv global $PYTHON_VERSION \ 22 | && pyenv rehash 23 | 24 | ENV PATH="/home/${DEVUSER}/.pyenv/shims:${PATH}" 25 | RUN python --version 26 | 27 | COPY --chown=${DEVUSER}:${DEVUSER} . ./ 28 | 29 | RUN npm install 30 | 31 | ENV PATH="/home/${DEVUSER}/.local/bin:${PATH}" 32 | RUN python -m pip install --user --quiet -r dev-requirements.txt --disable-pip-version-check 33 | 34 | ENV PY_TEST_CMD $PY_TEST_CMD 35 | CMD npm run $PY_TEST_CMD && npm run test:jest --runInBand 36 | -------------------------------------------------------------------------------- /test/fixtures/pipenv-project/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | "Jinja2" = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] -------------------------------------------------------------------------------- /test/fixtures/pipfile-without-dev-deps/Pipfile: -------------------------------------------------------------------------------- 1 | [packages] 2 | requests = {version = "==2.28.1"} 3 | 4 | [requires] 5 | python_version = "3.7" 6 | -------------------------------------------------------------------------------- /test/fixtures/poetry-project/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry-demo" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | flask = "*" 9 | 10 | [tool.poetry.dev-dependencies] 11 | pytest = "^3.4" 12 | 13 | -------------------------------------------------------------------------------- /test/fixtures/poetry-v2-project/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "out-of-sync" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [ 6 | {name = "Your Name",email = "you@example.com"} 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | dependencies = [ 11 | "jinja2 (>=3.1.2)", 12 | "isOdd (>=0.1.2)", 13 | ] 14 | [build-system] 15 | requires = ["poetry-core>=3.6"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /test/fixtures/setuptools-project/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name="test_package", 7 | version="1.0.2", 8 | packages=[ 9 | "Jinja2==2.7.2", 10 | ], 11 | ) -------------------------------------------------------------------------------- /test/fixtures/updated-manifest/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.2 2 | Django==2.0.1 3 | python-etcd==0.4.5 4 | Django-Select2==6.0.1 # this version installs with lowercase so it catches a previous bug in pip_resolve.py 5 | irc==16.2 # this has a cyclic dependecy (internal jaraco.text <==> jaraco.collections) 6 | testtools==\ 7 | 2.3.0 # this has a cycle (fixtures ==> testtols); 8 | ./packages/prometheus_client-0.6.0 9 | transitive>=1.1.1 # not directly required, pinned by Snyk to avoid a vulnerability 10 | -------------------------------------------------------------------------------- /test/fixtures/updated-manifests-with-python-markers/requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==2.4.2 2 | apscheduler==3.6.0 3 | asn1crypto==0.24.0 4 | astroid==1.6.6 5 | atomicwrites==1.3.0 6 | attrs==19.1.0 7 | automat==0.7.0 8 | backports.functools-lru-cache==1.5 ; python_version < '3.2' 9 | billiard==3.6.0.0 10 | celery==4.3.0 11 | certifi==2019.3.9 12 | cffi==1.12.3 13 | chardet==3.0.4 14 | click==7.1 ; python_version > '1.0' 15 | clickclick==1.2.2 16 | configparser==3.7.4 ; python_version == '2.7' 17 | connexion[swagger-ui]==2.2.0 18 | constantly==15.1.0 19 | cryptography==2.6.1 20 | cssselect==1.0.3 21 | cython==0.29.7 22 | enum34==1.1.6 ; python_version < '2.6' 23 | fastavro==0.21.21 24 | flask-apscheduler==1.11.0 25 | flask==1.0.2 26 | -------------------------------------------------------------------------------- /test/manual.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const plugin = require('../lib'); 3 | 4 | function main() { 5 | const targetFile = process.argv[2]; 6 | const root = process.argv[3] || '.'; 7 | 8 | plugin 9 | .inspect(root, targetFile) 10 | .then((result) => { 11 | console.log(JSON.stringify(result, null, 2)); 12 | }) 13 | .catch((error) => { 14 | console.log('Error:', error.stack); 15 | }); 16 | } 17 | 18 | main(); 19 | -------------------------------------------------------------------------------- /test/test-utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, @typescript-eslint/no-non-null-assertion */ 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import * as process from 'process'; 5 | import subProcess = require('../lib/dependencies/sub-process'); 6 | 7 | export { 8 | getActiveVenvName, 9 | activateVirtualenv, 10 | deactivateVirtualenv, 11 | ensureVirtualenv, 12 | pipInstall, 13 | pipenvInstall, 14 | pipUninstall, 15 | setupPyInstall, 16 | }; 17 | 18 | const binDirName = process.platform === 'win32' ? 'Scripts' : 'bin'; 19 | 20 | function getActiveVenvName() { 21 | return process.env.VIRTUAL_ENV 22 | ? path.basename(process.env.VIRTUAL_ENV) 23 | : null; 24 | } 25 | 26 | function activateVirtualenv(venvName: string) { 27 | const venvDir = path.join(path.resolve(__dirname), '.venvs', venvName); 28 | 29 | const binDir = path.resolve(venvDir, binDirName); 30 | 31 | const origProcessEnv = Object.assign({}, process.env); 32 | 33 | if (process.env.VIRTUAL_ENV) { 34 | const pathElements = process.env.PATH!.split(path.delimiter); 35 | const index = pathElements.indexOf(process.env.VIRTUAL_ENV); 36 | if (index > -1) { 37 | pathElements.splice(index, 1); 38 | } 39 | process.env.PATH = pathElements.join(path.delimiter); 40 | } 41 | 42 | // simulate the "activate" virtualenv script 43 | process.env.PATH = binDir + path.delimiter + process.env.PATH; 44 | process.env.VIRTUAL_ENV = venvDir; 45 | delete process.env.PYTHONHOME; 46 | 47 | return function revert() { 48 | process.env.VIRTUAL_ENV = origProcessEnv.VIRTUAL_ENV; 49 | process.env.PATH = origProcessEnv.PATH; 50 | process.env.PYTHONHOME = origProcessEnv.PYTHONHOME; 51 | }; 52 | } 53 | 54 | function deactivateVirtualenv() { 55 | if (getActiveVenvName() === null) { 56 | console.warn('Attempted to deactivate a virtualenv when none was active.'); 57 | // eslint-disable-next-line @typescript-eslint/no-empty-function 58 | return () => {}; 59 | } 60 | 61 | const origProcessEnv = Object.assign({}, process.env); 62 | 63 | // simulate the "deactivate" virtualenv script 64 | const pathElements = process.env.PATH!.split(path.delimiter); 65 | const venvBinDir = path.join(process.env.VIRTUAL_ENV!, binDirName); 66 | const index = pathElements.indexOf(venvBinDir); 67 | if (index > -1) { 68 | pathElements.splice(index, 1); 69 | } 70 | process.env.PATH = pathElements.join(path.delimiter); 71 | delete process.env.VIRTUAL_ENV; 72 | delete process.env.PYTHONHOME; 73 | 74 | return function revert() { 75 | process.env.VIRTUAL_ENV = origProcessEnv.VIRTUAL_ENV; 76 | process.env.PATH = origProcessEnv.PATH; 77 | process.env.PYTHONHOME = origProcessEnv.PYTHONHOME; 78 | }; 79 | } 80 | 81 | function ensureVirtualenv(venvName: string) { 82 | const venvsBaseDir = path.join(path.resolve(__dirname), '.venvs'); 83 | try { 84 | fs.accessSync(venvsBaseDir, fs.constants.R_OK); 85 | } catch (e) { 86 | fs.mkdirSync(venvsBaseDir); 87 | } 88 | 89 | const venvDir = path.join(venvsBaseDir, venvName); 90 | try { 91 | fs.accessSync(venvDir, fs.constants.R_OK); 92 | return false; 93 | } catch (e) { 94 | createVenv(venvDir); 95 | return true; 96 | } 97 | } 98 | 99 | function createVenv(venvDir: string) { 100 | // eslint-disable-next-line @typescript-eslint/no-empty-function 101 | let revert = () => {}; 102 | if (process.env.VIRTUAL_ENV) { 103 | revert = deactivateVirtualenv(); 104 | } 105 | try { 106 | let proc = subProcess.executeSync('python3 -m venv', [venvDir]); 107 | if (proc.status !== 0) { 108 | console.error(proc.stdout.toString() + '\n' + proc.stderr.toString()); 109 | throw new Error('Failed to create virtualenv in ' + venvDir); 110 | } 111 | if (process.env.PIP_VER) { 112 | proc = subProcess.executeSync( 113 | path.resolve(venvDir, binDirName, 'python3'), 114 | ['-m', 'pip', 'install', `pip==${process.env.PIP_VER}`] 115 | ); 116 | if (proc.status !== 0) { 117 | console.error(proc.stdout.toString() + '\n' + proc.stderr.toString()); 118 | throw new Error( 119 | 'Failed to install required pip version in virtualenv ' + venvDir 120 | ); 121 | } 122 | } 123 | } finally { 124 | revert(); 125 | } 126 | } 127 | 128 | function pipInstall() { 129 | const proc = subProcess.executeSync('pip', [ 130 | 'install', 131 | '-r', 132 | 'requirements.txt', 133 | '--disable-pip-version-check', 134 | ]); 135 | if (proc.status !== 0) { 136 | console.log('' + proc.stderr); 137 | throw new Error( 138 | 'Failed to install requirements with pip.' + 139 | ' venv = ' + 140 | JSON.stringify(getActiveVenvName()) 141 | ); 142 | } 143 | } 144 | 145 | function pipenvInstall() { 146 | updateSetuptools(); 147 | const proc = subProcess.executeSync('pipenv', ['install']); 148 | 149 | if (proc.status !== 0) { 150 | console.log('' + proc.stderr); 151 | throw new Error( 152 | 'Failed to install requirements with pipenv.' + 153 | ' venv = ' + 154 | JSON.stringify(getActiveVenvName()) 155 | ); 156 | } 157 | } 158 | 159 | function updateSetuptools() { 160 | const proc = subProcess.executeSync('python3', [ 161 | '-m', 162 | 'pip', 163 | 'install', 164 | '--upgrade', 165 | 'setuptools', 166 | ]); 167 | if (proc.status !== 0) { 168 | console.log('' + proc.stderr); 169 | throw new Error( 170 | 'Failed to update setuptools.' + 171 | ' venv = ' + 172 | JSON.stringify(getActiveVenvName()) 173 | ); 174 | } 175 | } 176 | 177 | function setupPyInstall() { 178 | updateSetuptools(); 179 | const proc = subProcess.executeSync('pip', ['install', '-e', '.']); 180 | if (proc.status !== 0) { 181 | console.log('' + proc.stderr); 182 | throw new Error( 183 | 'Failed to install requirements with setup.py.' + 184 | ' venv = ' + 185 | JSON.stringify(getActiveVenvName()) 186 | ); 187 | } 188 | } 189 | 190 | function pipUninstall(pkgName: string) { 191 | const proc = subProcess.executeSync('pip', ['uninstall', '-y', pkgName]); 192 | if (proc.status !== 0) { 193 | throw new Error( 194 | 'Failed to uninstall "' + 195 | pkgName + 196 | '" with pip.' + 197 | ' venv = ' + 198 | JSON.stringify(getActiveVenvName()) 199 | ); 200 | } 201 | } 202 | 203 | export function chdirWorkspaces(dir: string) { 204 | process.chdir(path.resolve(__dirname, 'workspaces', dir)); 205 | } 206 | -------------------------------------------------------------------------------- /test/unit/build-args.spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { buildArgs } from '../../lib/dependencies/inspect-implementation'; 3 | 4 | describe('build args', () => { 5 | it('should return expected args', () => { 6 | const result = buildArgs( 7 | 'requirements.txt', 8 | false, 9 | false, 10 | '../pysrc', 11 | false, 12 | ['-argOne', '-argTwo'] 13 | ); 14 | expect(result).toEqual([ 15 | `..${path.sep}pysrc${path.sep}pip_resolve.py`, 16 | 'requirements.txt', 17 | '-argOne', 18 | '-argTwo', 19 | ]); 20 | }); 21 | 22 | it('should return expected args when allowMissing is true', () => { 23 | const result = buildArgs( 24 | 'requirements.txt', 25 | true, 26 | false, 27 | '../pysrc', 28 | false, 29 | ['-argOne', '-argTwo'] 30 | ); 31 | expect(result).toEqual([ 32 | `..${path.sep}pysrc${path.sep}pip_resolve.py`, 33 | 'requirements.txt', 34 | '--allow-missing', 35 | '-argOne', 36 | '-argTwo', 37 | ]); 38 | }); 39 | 40 | it('should return expected args when includeDevDeps is true', () => { 41 | const result = buildArgs( 42 | 'requirements.txt', 43 | false, 44 | false, 45 | '../pysrc', 46 | true, 47 | ['-argOne', '-argTwo'] 48 | ); 49 | expect(result).toEqual([ 50 | `..${path.sep}pysrc${path.sep}pip_resolve.py`, 51 | 'requirements.txt', 52 | '--dev-deps', 53 | '-argOne', 54 | '-argTwo', 55 | ]); 56 | }); 57 | 58 | it('should return expected args when allowMissing and includeDevDeps are true', () => { 59 | const result = buildArgs( 60 | 'requirements.txt', 61 | true, 62 | false, 63 | '../pysrc', 64 | true, 65 | ['-argOne', '-argTwo'] 66 | ); 67 | expect(result).toEqual([ 68 | `..${path.sep}pysrc${path.sep}pip_resolve.py`, 69 | 'requirements.txt', 70 | '--allow-missing', 71 | '--dev-deps', 72 | '-argOne', 73 | '-argTwo', 74 | ]); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/fixtures/requirements.txt: -------------------------------------------------------------------------------- 1 | # no deps 2 | -------------------------------------------------------------------------------- /test/unit/inspect-implementation.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as tmp from 'tmp'; 3 | 4 | import { inspectInstalledDeps } from '../../lib/dependencies/inspect-implementation'; 5 | 6 | jest.mock('tmp'); 7 | 8 | describe('Test inspect-implementation.ts', () => { 9 | describe('inspectInstalledDeps', () => { 10 | const originalEnv = process.env; 11 | 12 | const dirSyncMock = tmp.dirSync as jest.MockedFunction; 13 | 14 | beforeEach(() => { 15 | jest.resetModules(); 16 | process.env = { ...originalEnv }; 17 | 18 | dirSyncMock.mockClear(); 19 | dirSyncMock.mockReturnValue({ 20 | name: 'tempDir', 21 | removeCallback: jest.fn(), 22 | }); 23 | }); 24 | 25 | afterEach(() => { 26 | try { 27 | fs.rmSync('tempDir', { recursive: true, force: true }); 28 | } catch (e) { 29 | // Ignore error 30 | } 31 | }); 32 | 33 | afterAll(() => { 34 | process.env = originalEnv; 35 | }); 36 | 37 | it('should call tmp.dirSync with tmpdir option when SNYK_TMP_PATH is set', async () => { 38 | const tmpDirPath = './test-temp-dir'; 39 | process.env.SNYK_TMP_PATH = tmpDirPath; 40 | 41 | await inspectInstalledDeps( 42 | 'python3', 43 | [], 44 | '.', 45 | 'test/unit/fixtures/requirements.txt', 46 | false, 47 | false, 48 | true 49 | ); 50 | 51 | expect(dirSyncMock).toHaveBeenCalledWith({ 52 | tmpdir: tmpDirPath, 53 | unsafeCleanup: true, 54 | }); 55 | expect(dirSyncMock).toHaveBeenCalledTimes(1); 56 | }); 57 | 58 | it('should call tmp.dirSync without tmpdir option when SNYK_TMP_PATH is not set', async () => { 59 | process.env.SNYK_TMP_PATH = undefined; 60 | 61 | await inspectInstalledDeps( 62 | 'python3', 63 | [], 64 | '.', 65 | 'test/unit/fixtures/requirements.txt', 66 | false, 67 | false, 68 | true 69 | ); 70 | 71 | expect(dirSyncMock).toHaveBeenCalledWith({ unsafeCleanup: true }); 72 | expect(dirSyncMock).toHaveBeenCalledTimes(1); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/unit/poetry.spec.ts: -------------------------------------------------------------------------------- 1 | import { getPoetryDependencies } from '../../lib/dependencies/poetry'; 2 | import * as path from 'path'; 3 | import { FILENAMES } from '../../lib/types'; 4 | import * as poetry from 'snyk-poetry-lockfile-parser'; 5 | 6 | describe('getPoetryDepencies', () => { 7 | it('should throw exception when manifest does not exist', async () => { 8 | expect.assertions(1); 9 | const root = 'rootPath'; 10 | const targetFile = 'non-existant-target-file'; 11 | try { 12 | await getPoetryDependencies('python', root, targetFile); 13 | } catch (e) { 14 | const expectedPath = path.join(root, FILENAMES.poetry.manifest); 15 | const expected = new Error(`Cannot find manifest file ${expectedPath}`); 16 | expect(e).toEqual(expected); 17 | } 18 | }); 19 | 20 | it('should throw exception when lockfile does not exist', async () => { 21 | expect.assertions(1); 22 | const root = path.join( 23 | __dirname, 24 | '..', 25 | 'workspaces', 26 | 'poetry-app-without-lockfile' 27 | ); 28 | const targetFile = FILENAMES.poetry.lockfile; 29 | try { 30 | await getPoetryDependencies('python', root, targetFile); 31 | } catch (e) { 32 | const expectedPath = path.join(root, FILENAMES.poetry.lockfile); 33 | const expected = new Error(`Cannot find lockfile ${expectedPath}`); 34 | expect(e).toEqual(expected); 35 | } 36 | }); 37 | 38 | it('should throw exception when lockfile parser throws exception', async () => { 39 | expect.assertions(1); 40 | const poetryError = new Error('some poetry error msg'); 41 | jest.spyOn(poetry, 'buildDepGraph').mockImplementation(() => { 42 | throw poetryError; 43 | }); 44 | 45 | const root = path.join(__dirname, '..', 'workspaces', 'poetry-app'); 46 | const targetFile = 'pyproject.toml'; 47 | try { 48 | await getPoetryDependencies('python', root, targetFile); 49 | } catch (e) { 50 | const expected = new Error( 51 | 'Error processing poetry project. some poetry error msg' 52 | ); 53 | expect(e).toEqual(expected); 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/unit/setup_file.spec.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import { executeSync } from '../../lib/dependencies/sub-process'; 3 | 4 | describe('Test setup_file.py', () => { 5 | it.each` 6 | setupPyContent 7 | ${'import setuptools;setuptools.setup(name="test")'} 8 | ${'from setuptools import setup;setup(name="test")'} 9 | `("parse works for '$setupPyContent'", ({ setupPyContent }) => { 10 | const result = executeSync( 11 | 'python3', 12 | ['-c', `from setup_file import parse; parse('${setupPyContent}')`], 13 | { cwd: path.resolve(__dirname, '../../pysrc') } 14 | ); 15 | 16 | expect(result.status).toBe(0); 17 | }); 18 | 19 | it('should work when --dev-deps is set but not dev-packages in Pipfile', async () => { 20 | const fixturePath = path.resolve( 21 | __dirname, 22 | '../fixtures/pipfile-without-dev-deps/Pipfile' 23 | ); 24 | 25 | const result = executeSync( 26 | 'python3', 27 | [ 28 | '-c', 29 | `from pip_resolve import get_requirements_for_pipenv; get_requirements_for_pipenv('${fixturePath}', True)`, 30 | ], 31 | { cwd: path.resolve(__dirname, '../../pysrc') } 32 | ); 33 | expect(result.status).toBe(0); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/sub-process.spec.ts: -------------------------------------------------------------------------------- 1 | import { executeSync } from '../../lib/dependencies/sub-process'; 2 | 3 | describe('Test sub-process.ts', () => { 4 | it('test restoring proxy setting in executeSync()', async () => { 5 | const expectedProxy = 'proxy'; 6 | const expectedProxyHTTPS = 'proxy2'; 7 | const expectedNoProxy = 'no-proxy'; 8 | 9 | process.env.SNYK_SYSTEM_HTTP_PROXY = 'http://127.0.0.1:3128'; 10 | process.env.SNYK_SYSTEM_HTTPS_PROXY = 'http://127.0.0.1:3129'; 11 | process.env.SNYK_SYSTEM_NO_PROXY = 'something.com'; 12 | 13 | const options = { 14 | env: { 15 | HTTP_PROXY: expectedProxy, 16 | HTTPS_PROXY: expectedProxyHTTPS, 17 | NO_PROXY: expectedNoProxy, 18 | }, 19 | }; 20 | 21 | let output = executeSync( 22 | 'python3', 23 | ['-c', "import os; print(os.environ['HTTP_PROXY'])"], 24 | options 25 | ); 26 | expect(output.stdout.toString().trim()).toEqual( 27 | process.env.SNYK_SYSTEM_HTTP_PROXY 28 | ); 29 | 30 | output = executeSync( 31 | 'python3', 32 | ['-c', "import os; print(os.environ['HTTPS_PROXY'])"], 33 | options 34 | ); 35 | expect(output.stdout.toString().trim()).toEqual( 36 | process.env.SNYK_SYSTEM_HTTPS_PROXY 37 | ); 38 | 39 | output = executeSync( 40 | 'python3', 41 | ['-c', "import os; print(os.environ['NO_PROXY'])"], 42 | options 43 | ); 44 | expect(output.stdout.toString().trim()).toEqual( 45 | process.env.SNYK_SYSTEM_NO_PROXY 46 | ); 47 | 48 | // ensure that options remain unchanged 49 | expect(options.env.HTTP_PROXY).toEqual(expectedProxy); 50 | expect(options.env.HTTPS_PROXY).toEqual(expectedProxyHTTPS); 51 | expect(options.env.NO_PROXY).toEqual(expectedNoProxy); 52 | 53 | delete process.env.SNYK_SYSTEM_HTTP_PROXY; 54 | delete process.env.SNYK_SYSTEM_HTTPS_PROXY; 55 | delete process.env.SNYK_SYSTEM_NO_PROXY; 56 | }); 57 | 58 | it('test executeSync()', async () => { 59 | const expectedProxy = 'proxy'; 60 | const expectedProxyHTTPS = 'proxy2'; 61 | const expectedNoProxy = 'no-proxy'; 62 | 63 | const options = { 64 | env: { 65 | HTTP_PROXY: expectedProxy, 66 | HTTPS_PROXY: expectedProxyHTTPS, 67 | NO_PROXY: expectedNoProxy, 68 | }, 69 | }; 70 | 71 | let output = executeSync( 72 | 'python3', 73 | ['-c', "import os; print(os.environ['HTTP_PROXY'])"], 74 | options 75 | ); 76 | expect(output.stdout.toString().trim()).toEqual(expectedProxy); 77 | 78 | output = executeSync( 79 | 'python3', 80 | ['-c', "import os; print(os.environ['HTTPS_PROXY'])"], 81 | options 82 | ); 83 | expect(output.stdout.toString().trim()).toEqual(expectedProxyHTTPS); 84 | 85 | output = executeSync( 86 | 'python3', 87 | ['-c', "import os; print(os.environ['NO_PROXY'])"], 88 | options 89 | ); 90 | expect(output.stdout.toString().trim()).toEqual(expectedNoProxy); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-bom/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.2 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-circular-deps/requirements.txt: -------------------------------------------------------------------------------- 1 | apache-airflow==2.8.1; python_version<"3.12" 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-canonicalization/requirements.txt: -------------------------------------------------------------------------------- 1 | zope.interface==5.4.0 2 | twisted==23.10.0 -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-conditional/requirements.txt: -------------------------------------------------------------------------------- 1 | pypiwin32==223; sys_platform == 'win32' 2 | posix_ipc==1.0.0; sys_platform != 'win32' 3 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-editable/.gitignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-editable/requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/snyk-fixtures/python-pypi-package-simple@v1.0.0#egg=simple==v1.0.0 2 | -e git+https://github.com/snyk-fixtures/python-pypi-package-sample-subdir#egg=sample&subdirectory=subdir 3 | posix_ipc==1.0.0; sys_platform != 'win32' 4 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-not-installed/requirements.txt: -------------------------------------------------------------------------------- 1 | awss==0.9.13 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-with-dashes/requirements.txt: -------------------------------------------------------------------------------- 1 | dj_database_url==0.4.2 2 | posix_ipc==1.0.0; sys_platform != 'win32' 3 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-deps-with-urls/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.2 2 | https://files.pythonhosted.org/packages/d0/d7/4806fd165c27716f02a3a9c23d207854b8a3ed884db53c2781b92bd8d4f4/whl.setup-0.2-py2.py3-none-any.whl 3 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-dev-alpha-beta-python-version/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.31.0 ; python_version >= "3.8.dev0" 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-dir/boto3/README.md: -------------------------------------------------------------------------------- 1 | this is just a readme file 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-dir/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-nonexistent-file/lib/nonexistent/not-setup-file.txt: -------------------------------------------------------------------------------- 1 | This is a dummy file -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-nonexistent-file/requirements.txt: -------------------------------------------------------------------------------- 1 | ./lib/nonexistent -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-whl-file/README.md: -------------------------------------------------------------------------------- 1 | The Python wheel file `./lib/my_package-0.1.0-py3-none-any.whl` was created based off of the `setup.py` file in the `project` directory by running `python setup.py bdist_wheel`. 2 | It was initially created at `/project/dist/my_package-0.1.0-py3-none-any.whl`, but moved to the `lib` directory. -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-whl-file/lib/my_package-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snyk/snyk-python-plugin/1dbb91148981c9c34288f8c0f93616b134c246b1/test/workspaces/pip-app-local-whl-file/lib/my_package-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-whl-file/project/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | setup( 3 | name='my_package', 4 | version='0.1.0', 5 | description='My awesome package', 6 | author='Your Name', 7 | author_email='your_email@example.com', 8 | packages=find_packages(), 9 | install_requires=['numpy', 'pandas'], 10 | ) -------------------------------------------------------------------------------- /test/workspaces/pip-app-local-whl-file/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.31.0 2 | ./lib/my_package-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /test/workspaces/pip-app-optional-dependencies/requirements.txt: -------------------------------------------------------------------------------- 1 | opentelemetry-distro[otlp] == 0.35b0 -------------------------------------------------------------------------------- /test/workspaces/pip-app-trusted-host/requirements.txt: -------------------------------------------------------------------------------- 1 | --trusted-host pypi.fury.io 2 | 3 | Jinja2==2.7.2 4 | Django==1.6.1 5 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-with-openapi_spec_validator/requirements.txt: -------------------------------------------------------------------------------- 1 | openapi_spec_validator 2 | jsonschema==4.23.0 3 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-with-python-markers/requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==2.4.2 2 | apscheduler==3.6.0 3 | asn1crypto==0.24.0 4 | lazy-object-proxy==1.6.0 5 | astroid==1.6.6 6 | atomicwrites==1.3.0 7 | attrs==19.1.0 8 | automat==0.7.0 9 | backports.functools-lru-cache==1.5 ; python_version < '3.2' 10 | billiard==3.6.0.0 11 | celery==4.3.0 12 | certifi==2019.3.9 13 | cffi==1.12.3 14 | chardet==3.0.4 15 | click==7.0 ; python_version > '1.0' 16 | clickclick==1.2.2 17 | configparser==3.7.4 ; python_version == '2.7' 18 | connexion[swagger-ui]==2.2.0 ; python_version > '2.7' 19 | constantly==15.1.0 20 | cryptography==2.6.1 21 | cssselect==1.0.3 22 | cython==0.29.7 ; python_version < '3.9' 23 | enum34==1.1.6 ; python_version < '2.6' 24 | fastavro==0.21.21 ; python_version <= '3.7' 25 | flask-apscheduler==1.11.0 26 | flask==1.0.2 27 | -------------------------------------------------------------------------------- /test/workspaces/pip-app-without-markupsafe/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.2 2 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: prometheus_client 3 | Version: 0.6.0 4 | Summary: Python client for the Prometheus monitoring system. 5 | Home-page: https://github.com/prometheus/client_python 6 | Author: Brian Brazil 7 | Author-email: brian.brazil@robustperception.io 8 | License: Apache Software License 2.0 9 | Description-Content-Type: UNKNOWN 10 | Description: See https://github.com/prometheus/client_python/blob/master/README.md for documentation. 11 | Keywords: prometheus monitoring instrumentation client 12 | Platform: UNKNOWN 13 | Classifier: Development Status :: 4 - Beta 14 | Classifier: Intended Audience :: Developers 15 | Classifier: Intended Audience :: Information Technology 16 | Classifier: Intended Audience :: System Administrators 17 | Classifier: Programming Language :: Python 18 | Classifier: Programming Language :: Python :: 2 19 | Classifier: Programming Language :: Python :: 2.6 20 | Classifier: Programming Language :: Python :: 2.7 21 | Classifier: Programming Language :: Python :: 3 22 | Classifier: Programming Language :: Python :: 3.4 23 | Classifier: Programming Language :: Python :: 3.5 24 | Classifier: Programming Language :: Python :: 3.6 25 | Classifier: Programming Language :: Python :: Implementation :: CPython 26 | Classifier: Programming Language :: Python :: Implementation :: PyPy 27 | Classifier: Topic :: System :: Monitoring 28 | Classifier: License :: OSI Approved :: Apache Software License 29 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from . import core 4 | from . import exposition 5 | from . import gc_collector 6 | from . import platform_collector 7 | from . import process_collector 8 | from . import registry 9 | from . import metrics_core 10 | from . import metrics 11 | 12 | __all__ = ['Counter', 'Gauge', 'Summary', 'Histogram', 'Info', 'Enum'] 13 | 14 | CollectorRegistry = registry.CollectorRegistry 15 | REGISTRY = registry.REGISTRY 16 | Metric = metrics_core.Metric 17 | Counter = metrics.Counter 18 | Gauge = metrics.Gauge 19 | Summary = metrics.Summary 20 | Histogram = metrics.Histogram 21 | Info = metrics.Info 22 | Enum = metrics.Enum 23 | 24 | CONTENT_TYPE_LATEST = exposition.CONTENT_TYPE_LATEST 25 | generate_latest = exposition.generate_latest 26 | MetricsHandler = exposition.MetricsHandler 27 | make_wsgi_app = exposition.make_wsgi_app 28 | start_http_server = exposition.start_http_server 29 | start_wsgi_server = exposition.start_wsgi_server 30 | write_to_textfile = exposition.write_to_textfile 31 | push_to_gateway = exposition.push_to_gateway 32 | pushadd_to_gateway = exposition.pushadd_to_gateway 33 | delete_from_gateway = exposition.delete_from_gateway 34 | instance_ip_grouping_key = exposition.instance_ip_grouping_key 35 | 36 | ProcessCollector = process_collector.ProcessCollector 37 | PROCESS_COLLECTOR = process_collector.PROCESS_COLLECTOR 38 | 39 | PlatformCollector = platform_collector.PlatformCollector 40 | PLATFORM_COLLECTOR = platform_collector.PLATFORM_COLLECTOR 41 | 42 | GCCollector = gc_collector.GCCollector 43 | GC_COLLECTOR = gc_collector.GC_COLLECTOR 44 | 45 | if __name__ == '__main__': 46 | c = Counter('cc', 'A counter') 47 | c.inc() 48 | 49 | g = Gauge('gg', 'A gauge') 50 | g.set(17) 51 | 52 | s = Summary('ss', 'A summary', ['a', 'b']) 53 | s.labels('c', 'd').observe(17) 54 | 55 | h = Histogram('hh', 'A histogram') 56 | h.observe(.6) 57 | 58 | start_http_server(8000) 59 | import time 60 | while True: 61 | time.sleep(1) 62 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/bridge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snyk/snyk-python-plugin/1dbb91148981c9c34288f8c0f93616b134c246b1/test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/bridge/__init__.py -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/bridge/graphite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import unicode_literals 3 | 4 | import logging 5 | import re 6 | import socket 7 | import threading 8 | import time 9 | from timeit import default_timer 10 | 11 | from ..registry import REGISTRY 12 | 13 | # Roughly, have to keep to what works as a file name. 14 | # We also remove periods, so labels can be distinguished. 15 | 16 | _INVALID_GRAPHITE_CHARS = re.compile(r"[^a-zA-Z0-9_-]") 17 | 18 | 19 | def _sanitize(s): 20 | return _INVALID_GRAPHITE_CHARS.sub('_', s) 21 | 22 | 23 | class _RegularPush(threading.Thread): 24 | def __init__(self, pusher, interval, prefix): 25 | super(_RegularPush, self).__init__() 26 | self._pusher = pusher 27 | self._interval = interval 28 | self._prefix = prefix 29 | 30 | def run(self): 31 | wait_until = default_timer() 32 | while True: 33 | while True: 34 | now = default_timer() 35 | if now >= wait_until: 36 | # May need to skip some pushes. 37 | while wait_until < now: 38 | wait_until += self._interval 39 | break 40 | # time.sleep can return early. 41 | time.sleep(wait_until - now) 42 | try: 43 | self._pusher.push(prefix=self._prefix) 44 | except IOError: 45 | logging.exception("Push failed") 46 | 47 | 48 | class GraphiteBridge(object): 49 | def __init__(self, address, registry=REGISTRY, timeout_seconds=30, _timer=time.time): 50 | self._address = address 51 | self._registry = registry 52 | self._timeout = timeout_seconds 53 | self._timer = _timer 54 | 55 | def push(self, prefix=''): 56 | now = int(self._timer()) 57 | output = [] 58 | 59 | prefixstr = '' 60 | if prefix: 61 | prefixstr = prefix + '.' 62 | 63 | for metric in self._registry.collect(): 64 | for s in metric.samples: 65 | if s.labels: 66 | labelstr = '.' + '.'.join( 67 | ['{0}.{1}'.format( 68 | _sanitize(k), _sanitize(v)) 69 | for k, v in sorted(s.labels.items())]) 70 | else: 71 | labelstr = '' 72 | output.append('{0}{1}{2} {3} {4}\n'.format( 73 | prefixstr, _sanitize(s.name), labelstr, float(s.value), now)) 74 | 75 | conn = socket.create_connection(self._address, self._timeout) 76 | conn.sendall(''.join(output).encode('ascii')) 77 | conn.close() 78 | 79 | def start(self, interval=60.0, prefix=''): 80 | t = _RegularPush(self, interval, prefix) 81 | t.daemon = True 82 | t.start() 83 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/context_managers.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from timeit import default_timer 4 | 5 | from .decorator import decorate 6 | 7 | 8 | class ExceptionCounter(object): 9 | def __init__(self, counter, exception): 10 | self._counter = counter 11 | self._exception = exception 12 | 13 | def __enter__(self): 14 | pass 15 | 16 | def __exit__(self, typ, value, traceback): 17 | if isinstance(value, self._exception): 18 | self._counter.inc() 19 | 20 | def __call__(self, f): 21 | def wrapped(func, *args, **kwargs): 22 | with self: 23 | return func(*args, **kwargs) 24 | 25 | return decorate(f, wrapped) 26 | 27 | 28 | class InprogressTracker(object): 29 | def __init__(self, gauge): 30 | self._gauge = gauge 31 | 32 | def __enter__(self): 33 | self._gauge.inc() 34 | 35 | def __exit__(self, typ, value, traceback): 36 | self._gauge.dec() 37 | 38 | def __call__(self, f): 39 | def wrapped(func, *args, **kwargs): 40 | with self: 41 | return func(*args, **kwargs) 42 | 43 | return decorate(f, wrapped) 44 | 45 | 46 | class Timer(object): 47 | def __init__(self, callback): 48 | self._callback = callback 49 | 50 | def _new_timer(self): 51 | return self.__class__(self._callback) 52 | 53 | def __enter__(self): 54 | self._start = default_timer() 55 | 56 | def __exit__(self, typ, value, traceback): 57 | # Time can go backwards. 58 | duration = max(default_timer() - self._start, 0) 59 | self._callback(duration) 60 | 61 | def __call__(self, f): 62 | def wrapped(func, *args, **kwargs): 63 | # Obtaining new instance of timer every time 64 | # ensures thread safety and reentrancy. 65 | with self._new_timer(): 66 | return func(*args, **kwargs) 67 | 68 | return decorate(f, wrapped) 69 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/core.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary 4 | from .metrics_core import ( 5 | CounterMetricFamily, GaugeHistogramMetricFamily, GaugeMetricFamily, 6 | HistogramMetricFamily, InfoMetricFamily, Metric, Sample, 7 | StateSetMetricFamily, SummaryMetricFamily, UnknownMetricFamily, 8 | UntypedMetricFamily, 9 | ) 10 | from .registry import CollectorRegistry, REGISTRY 11 | from .samples import Exemplar, Sample, Timestamp 12 | 13 | __all__ = ( 14 | 'CollectorRegistry', 15 | 'Counter', 16 | 'CounterMetricFamily', 17 | 'Enum', 18 | 'Exemplar', 19 | 'Gauge', 20 | 'GaugeHistogramMetricFamily', 21 | 'GaugeMetricFamily', 22 | 'Histogram', 23 | 'HistogramMetricFamily', 24 | 'Info', 25 | 'InfoMetricFamily', 26 | 'Metric', 27 | 'REGISTRY', 28 | 'Sample', 29 | 'StateSetMetricFamily', 30 | 'Summary', 31 | 'SummaryMetricFamily', 32 | 'Timestamp', 33 | 'UnknownMetricFamily', 34 | 'UntypedMetricFamily', 35 | ) 36 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/gc_collector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import unicode_literals 4 | 5 | import gc 6 | 7 | from .metrics_core import CounterMetricFamily 8 | from .registry import REGISTRY 9 | 10 | 11 | class GCCollector(object): 12 | """Collector for Garbage collection statistics.""" 13 | 14 | def __init__(self, registry=REGISTRY): 15 | if not hasattr(gc, 'get_stats'): 16 | return 17 | registry.register(self) 18 | 19 | def collect(self): 20 | collected = CounterMetricFamily( 21 | 'python_gc_objects_collected', 22 | 'Objects collected during gc', 23 | labels=['generation'], 24 | ) 25 | uncollectable = CounterMetricFamily( 26 | 'python_gc_objects_uncollectable', 27 | 'Uncollectable object found during GC', 28 | labels=['generation'], 29 | ) 30 | 31 | collections = CounterMetricFamily( 32 | 'python_gc_collections', 33 | 'Number of times this generation was collected', 34 | labels=['generation'], 35 | ) 36 | 37 | for generation, stat in enumerate(gc.get_stats()): 38 | generation = str(generation) 39 | collected.add_metric([generation], value=stat['collected']) 40 | uncollectable.add_metric([generation], value=stat['uncollectable']) 41 | collections.add_metric([generation], value=stat['collections']) 42 | 43 | return [collected, uncollectable, collections] 44 | 45 | 46 | GC_COLLECTOR = GCCollector() 47 | """Default GCCollector in default Registry REGISTRY.""" 48 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/mmap_dict.py: -------------------------------------------------------------------------------- 1 | import json 2 | import mmap 3 | import os 4 | import struct 5 | 6 | _INITIAL_MMAP_SIZE = 1 << 20 7 | _pack_integer_func = struct.Struct(b'i').pack 8 | _pack_double_func = struct.Struct(b'd').pack 9 | _unpack_integer = struct.Struct(b'i').unpack_from 10 | _unpack_double = struct.Struct(b'd').unpack_from 11 | 12 | 13 | # struct.pack_into has atomicity issues because it will temporarily write 0 into 14 | # the mmap, resulting in false reads to 0 when experiencing a lot of writes. 15 | # Using direct assignment solves this issue. 16 | 17 | def _pack_double(data, pos, value): 18 | data[pos:pos + 8] = _pack_double_func(value) 19 | 20 | 21 | def _pack_integer(data, pos, value): 22 | data[pos:pos + 4] = _pack_integer_func(value) 23 | 24 | 25 | 26 | class MmapedDict(object): 27 | """A dict of doubles, backed by an mmapped file. 28 | 29 | The file starts with a 4 byte int, indicating how much of it is used. 30 | Then 4 bytes of padding. 31 | There's then a number of entries, consisting of a 4 byte int which is the 32 | size of the next field, a utf-8 encoded string key, padding to a 8 byte 33 | alignment, and then a 8 byte float which is the value. 34 | 35 | Not thread safe. 36 | """ 37 | 38 | def __init__(self, filename, read_mode=False): 39 | self._f = open(filename, 'rb' if read_mode else 'a+b') 40 | self._fname = filename 41 | if os.fstat(self._f.fileno()).st_size == 0: 42 | self._f.truncate(_INITIAL_MMAP_SIZE) 43 | self._capacity = os.fstat(self._f.fileno()).st_size 44 | self._m = mmap.mmap(self._f.fileno(), self._capacity, access=mmap.ACCESS_READ if read_mode else mmap.ACCESS_WRITE) 45 | 46 | self._positions = {} 47 | self._used = _unpack_integer(self._m, 0)[0] 48 | if self._used == 0: 49 | self._used = 8 50 | _pack_integer(self._m, 0, self._used) 51 | else: 52 | if not read_mode: 53 | for key, _, pos in self._read_all_values(): 54 | self._positions[key] = pos 55 | 56 | def _init_value(self, key): 57 | """Initialize a value. Lock must be held by caller.""" 58 | encoded = key.encode('utf-8') 59 | # Pad to be 8-byte aligned. 60 | padded = encoded + (b' ' * (8 - (len(encoded) + 4) % 8)) 61 | value = struct.pack('i{0}sd'.format(len(padded)).encode(), len(encoded), padded, 0.0) 62 | while self._used + len(value) > self._capacity: 63 | self._capacity *= 2 64 | self._f.truncate(self._capacity) 65 | self._m = mmap.mmap(self._f.fileno(), self._capacity) 66 | self._m[self._used:self._used + len(value)] = value 67 | 68 | # Update how much space we've used. 69 | self._used += len(value) 70 | _pack_integer(self._m, 0, self._used) 71 | self._positions[key] = self._used - 8 72 | 73 | def _read_all_values(self): 74 | """Yield (key, value, pos). No locking is performed.""" 75 | 76 | pos = 8 77 | 78 | # cache variables to local ones and prevent attributes lookup 79 | # on every loop iteration 80 | used = self._used 81 | data = self._m 82 | unpack_from = struct.unpack_from 83 | 84 | while pos < used: 85 | encoded_len = _unpack_integer(data, pos)[0] 86 | # check we are not reading beyond bounds 87 | if encoded_len + pos > used: 88 | msg = 'Read beyond file size detected, %s is corrupted.' 89 | raise RuntimeError(msg % self._fname) 90 | pos += 4 91 | encoded = unpack_from(('%ss' % encoded_len).encode(), data, pos)[0] 92 | padded_len = encoded_len + (8 - (encoded_len + 4) % 8) 93 | pos += padded_len 94 | value = _unpack_double(data, pos)[0] 95 | yield encoded.decode('utf-8'), value, pos 96 | pos += 8 97 | 98 | def read_all_values(self): 99 | """Yield (key, value, pos). No locking is performed.""" 100 | for k, v, _ in self._read_all_values(): 101 | yield k, v 102 | 103 | def read_value(self, key): 104 | if key not in self._positions: 105 | self._init_value(key) 106 | pos = self._positions[key] 107 | # We assume that reading from an 8 byte aligned value is atomic 108 | return _unpack_double(self._m, pos)[0] 109 | 110 | def write_value(self, key, value): 111 | if key not in self._positions: 112 | self._init_value(key) 113 | pos = self._positions[key] 114 | # We assume that writing to an 8 byte aligned value is atomic 115 | _pack_double(self._m, pos, value) 116 | 117 | def close(self): 118 | if self._f: 119 | self._m.close() 120 | self._m = None 121 | self._f.close() 122 | self._f = None 123 | 124 | 125 | def mmap_key(metric_name, name, labelnames, labelvalues): 126 | """Format a key for use in the mmap file.""" 127 | # ensure labels are in consistent order for identity 128 | labels = dict(zip(labelnames, labelvalues)) 129 | return json.dumps([metric_name, name, labels], sort_keys=True) 130 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/multiprocess.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import unicode_literals 4 | 5 | from collections import defaultdict 6 | import glob 7 | import json 8 | import os 9 | 10 | from .metrics_core import Metric 11 | from .mmap_dict import MmapedDict 12 | from .samples import Sample 13 | from .utils import floatToGoString 14 | 15 | 16 | class MultiProcessCollector(object): 17 | """Collector for files for multi-process mode.""" 18 | 19 | def __init__(self, registry, path=None): 20 | if path is None: 21 | path = os.environ.get('prometheus_multiproc_dir') 22 | if not path or not os.path.isdir(path): 23 | raise ValueError('env prometheus_multiproc_dir is not set or not a directory') 24 | self._path = path 25 | if registry: 26 | registry.register(self) 27 | 28 | def collect(self): 29 | files = glob.glob(os.path.join(self._path, '*.db')) 30 | return self.merge(files, accumulate=True) 31 | 32 | def merge(self, files, accumulate=True): 33 | """Merge metrics from given mmap files. 34 | 35 | By default, histograms are accumulated, as per prometheus wire format. 36 | But if writing the merged data back to mmap files, use 37 | accumulate=False to avoid compound accumulation. 38 | """ 39 | metrics = {} 40 | for f in files: 41 | parts = os.path.basename(f).split('_') 42 | typ = parts[0] 43 | d = MmapedDict(f, read_mode=True) 44 | for key, value in d.read_all_values(): 45 | metric_name, name, labels = json.loads(key) 46 | labels_key = tuple(sorted(labels.items())) 47 | 48 | metric = metrics.get(metric_name) 49 | if metric is None: 50 | metric = Metric(metric_name, 'Multiprocess metric', typ) 51 | metrics[metric_name] = metric 52 | 53 | if typ == 'gauge': 54 | pid = parts[2][:-3] 55 | metric._multiprocess_mode = parts[1] 56 | metric.add_sample(name, labels_key + (('pid', pid), ), value) 57 | else: 58 | # The duplicates and labels are fixed in the next for. 59 | metric.add_sample(name, labels_key, value) 60 | d.close() 61 | 62 | for metric in metrics.values(): 63 | samples = defaultdict(float) 64 | buckets = {} 65 | for s in metric.samples: 66 | name, labels, value = s.name, s.labels, s.value 67 | if metric.type == 'gauge': 68 | without_pid = tuple(l for l in labels if l[0] != 'pid') 69 | if metric._multiprocess_mode == 'min': 70 | current = samples.setdefault((name, without_pid), value) 71 | if value < current: 72 | samples[(s.name, without_pid)] = value 73 | elif metric._multiprocess_mode == 'max': 74 | current = samples.setdefault((name, without_pid), value) 75 | if value > current: 76 | samples[(s.name, without_pid)] = value 77 | elif metric._multiprocess_mode == 'livesum': 78 | samples[(name, without_pid)] += value 79 | else: # all/liveall 80 | samples[(name, labels)] = value 81 | 82 | elif metric.type == 'histogram': 83 | bucket = tuple(float(l[1]) for l in labels if l[0] == 'le') 84 | if bucket: 85 | # _bucket 86 | without_le = tuple(l for l in labels if l[0] != 'le') 87 | buckets.setdefault(without_le, {}) 88 | buckets[without_le].setdefault(bucket[0], 0.0) 89 | buckets[without_le][bucket[0]] += value 90 | else: 91 | # _sum/_count 92 | samples[(s.name, labels)] += value 93 | 94 | else: 95 | # Counter and Summary. 96 | samples[(s.name, labels)] += value 97 | 98 | # Accumulate bucket values. 99 | if metric.type == 'histogram': 100 | for labels, values in buckets.items(): 101 | acc = 0.0 102 | for bucket, value in sorted(values.items()): 103 | sample_key = ( 104 | metric.name + '_bucket', 105 | labels + (('le', floatToGoString(bucket)), ), 106 | ) 107 | if accumulate: 108 | acc += value 109 | samples[sample_key] = acc 110 | else: 111 | samples[sample_key] = value 112 | if accumulate: 113 | samples[(metric.name + '_count', labels)] = acc 114 | 115 | # Convert to correct sample format. 116 | metric.samples = [Sample(name, dict(labels), value) for (name, labels), value in samples.items()] 117 | return metrics.values() 118 | 119 | 120 | def mark_process_dead(pid, path=None): 121 | """Do bookkeeping for when one process dies in a multi-process setup.""" 122 | if path is None: 123 | path = os.environ.get('prometheus_multiproc_dir') 124 | for f in glob.glob(os.path.join(path, 'gauge_livesum_{0}.db'.format(pid))): 125 | os.remove(f) 126 | for f in glob.glob(os.path.join(path, 'gauge_liveall_{0}.db'.format(pid))): 127 | os.remove(f) 128 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/openmetrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snyk/snyk-python-plugin/1dbb91148981c9c34288f8c0f93616b134c246b1/test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/openmetrics/__init__.py -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/openmetrics/exposition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import unicode_literals 4 | 5 | from ..utils import floatToGoString 6 | 7 | CONTENT_TYPE_LATEST = str('application/openmetrics-text; version=0.0.1; charset=utf-8') 8 | '''Content type of the latest OpenMetrics text format''' 9 | 10 | 11 | def generate_latest(registry): 12 | '''Returns the metrics from the registry in latest text format as a string.''' 13 | output = [] 14 | for metric in registry.collect(): 15 | try: 16 | mname = metric.name 17 | output.append('# HELP {0} {1}\n'.format( 18 | mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))) 19 | output.append('# TYPE {0} {1}\n'.format(mname, metric.type)) 20 | if metric.unit: 21 | output.append('# UNIT {0} {1}\n'.format(mname, metric.unit)) 22 | for s in metric.samples: 23 | if s.labels: 24 | labelstr = '{{{0}}}'.format(','.join( 25 | ['{0}="{1}"'.format( 26 | k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) 27 | for k, v in sorted(s.labels.items())])) 28 | else: 29 | labelstr = '' 30 | if s.exemplar: 31 | if metric.type not in ('histogram', 'gaugehistogram') or not s.name.endswith('_bucket'): 32 | raise ValueError("Metric {0} has exemplars, but is not a histogram bucket".format(metric.name)) 33 | labels = '{{{0}}}'.format(','.join( 34 | ['{0}="{1}"'.format( 35 | k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) 36 | for k, v in sorted(s.exemplar.labels.items())])) 37 | if s.exemplar.timestamp is not None: 38 | exemplarstr = ' # {0} {1} {2}'.format( 39 | labels, 40 | floatToGoString(s.exemplar.value), 41 | s.exemplar.timestamp, 42 | ) 43 | else: 44 | exemplarstr = ' # {0} {1}'.format( 45 | labels, 46 | floatToGoString(s.exemplar.value), 47 | ) 48 | else: 49 | exemplarstr = '' 50 | timestamp = '' 51 | if s.timestamp is not None: 52 | timestamp = ' {0}'.format(s.timestamp) 53 | output.append('{0}{1} {2}{3}{4}\n'.format( 54 | s.name, 55 | labelstr, 56 | floatToGoString(s.value), 57 | timestamp, 58 | exemplarstr, 59 | )) 60 | except Exception as exception: 61 | exception.args = (exception.args or ('',)) + (metric,) 62 | raise 63 | 64 | output.append('# EOF\n') 65 | return ''.join(output).encode('utf-8') 66 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import unicode_literals 4 | 5 | import re 6 | 7 | from .metrics_core import Metric 8 | from .samples import Sample 9 | 10 | try: 11 | import StringIO 12 | except ImportError: 13 | # Python 3 14 | import io as StringIO 15 | 16 | 17 | 18 | def text_string_to_metric_families(text): 19 | """Parse Prometheus text format from a unicode string. 20 | 21 | See text_fd_to_metric_families. 22 | """ 23 | for metric_family in text_fd_to_metric_families(StringIO.StringIO(text)): 24 | yield metric_family 25 | 26 | 27 | ESCAPE_SEQUENCES = { 28 | '\\\\': '\\', 29 | '\\n': '\n', 30 | '\\"': '"', 31 | } 32 | 33 | 34 | def replace_escape_sequence(match): 35 | return ESCAPE_SEQUENCES[match.group(0)] 36 | 37 | 38 | HELP_ESCAPING_RE = re.compile(r'\\[\\n]') 39 | ESCAPING_RE = re.compile(r'\\[\\n"]') 40 | 41 | 42 | def _replace_help_escaping(s): 43 | return HELP_ESCAPING_RE.sub(replace_escape_sequence, s) 44 | 45 | 46 | def _replace_escaping(s): 47 | return ESCAPING_RE.sub(replace_escape_sequence, s) 48 | 49 | 50 | def _is_character_escaped(s, charpos): 51 | num_bslashes = 0 52 | while (charpos > num_bslashes and 53 | s[charpos - 1 - num_bslashes] == '\\'): 54 | num_bslashes += 1 55 | return num_bslashes % 2 == 1 56 | 57 | 58 | def _parse_labels(labels_string): 59 | labels = {} 60 | # Return if we don't have valid labels 61 | if "=" not in labels_string: 62 | return labels 63 | 64 | escaping = False 65 | if "\\" in labels_string: 66 | escaping = True 67 | 68 | # Copy original labels 69 | sub_labels = labels_string 70 | try: 71 | # Process one label at a time 72 | while sub_labels: 73 | # The label name is before the equal 74 | value_start = sub_labels.index("=") 75 | label_name = sub_labels[:value_start] 76 | sub_labels = sub_labels[value_start + 1:].lstrip() 77 | # Find the first quote after the equal 78 | quote_start = sub_labels.index('"') + 1 79 | value_substr = sub_labels[quote_start:] 80 | 81 | # Find the last unescaped quote 82 | i = 0 83 | while i < len(value_substr): 84 | i = value_substr.index('"', i) 85 | if not _is_character_escaped(value_substr, i): 86 | break 87 | i += 1 88 | 89 | # The label value is inbetween the first and last quote 90 | quote_end = i + 1 91 | label_value = sub_labels[quote_start:quote_end] 92 | # Replace escaping if needed 93 | if escaping: 94 | label_value = _replace_escaping(label_value) 95 | labels[label_name.strip()] = label_value 96 | 97 | # Remove the processed label from the sub-slice for next iteration 98 | sub_labels = sub_labels[quote_end + 1:] 99 | next_comma = sub_labels.find(",") + 1 100 | sub_labels = sub_labels[next_comma:].lstrip() 101 | 102 | return labels 103 | 104 | except ValueError: 105 | raise ValueError("Invalid labels: %s" % labels_string) 106 | 107 | 108 | # If we have multiple values only consider the first 109 | def _parse_value(s): 110 | s = s.lstrip() 111 | separator = " " 112 | if separator not in s: 113 | separator = "\t" 114 | i = s.find(separator) 115 | if i == -1: 116 | return s 117 | return s[:i] 118 | 119 | 120 | def _parse_sample(text): 121 | # Detect the labels in the text 122 | try: 123 | label_start, label_end = text.index("{"), text.rindex("}") 124 | # The name is before the labels 125 | name = text[:label_start].strip() 126 | # We ignore the starting curly brace 127 | label = text[label_start + 1:label_end] 128 | # The value is after the label end (ignoring curly brace and space) 129 | value = float(_parse_value(text[label_end + 2:])) 130 | return Sample(name, _parse_labels(label), value) 131 | 132 | # We don't have labels 133 | except ValueError: 134 | # Detect what separator is used 135 | separator = " " 136 | if separator not in text: 137 | separator = "\t" 138 | name_end = text.index(separator) 139 | name = text[:name_end] 140 | # The value is after the name 141 | value = float(_parse_value(text[name_end:])) 142 | return Sample(name, {}, value) 143 | 144 | 145 | def text_fd_to_metric_families(fd): 146 | """Parse Prometheus text format from a file descriptor. 147 | 148 | This is a laxer parser than the main Go parser, 149 | so successful parsing does not imply that the parsed 150 | text meets the specification. 151 | 152 | Yields Metric's. 153 | """ 154 | name = '' 155 | documentation = '' 156 | typ = 'untyped' 157 | samples = [] 158 | allowed_names = [] 159 | 160 | def build_metric(name, documentation, typ, samples): 161 | # Munge counters into OpenMetrics representation 162 | # used internally. 163 | if typ == 'counter': 164 | if name.endswith('_total'): 165 | name = name[:-6] 166 | else: 167 | new_samples = [] 168 | for s in samples: 169 | new_samples.append(Sample(s[0] + '_total', *s[1:])) 170 | samples = new_samples 171 | metric = Metric(name, documentation, typ) 172 | metric.samples = samples 173 | return metric 174 | 175 | for line in fd: 176 | line = line.strip() 177 | 178 | if line.startswith('#'): 179 | parts = line.split(None, 3) 180 | if len(parts) < 2: 181 | continue 182 | if parts[1] == 'HELP': 183 | if parts[2] != name: 184 | if name != '': 185 | yield build_metric(name, documentation, typ, samples) 186 | # New metric 187 | name = parts[2] 188 | typ = 'untyped' 189 | samples = [] 190 | allowed_names = [parts[2]] 191 | if len(parts) == 4: 192 | documentation = _replace_help_escaping(parts[3]) 193 | else: 194 | documentation = '' 195 | elif parts[1] == 'TYPE': 196 | if parts[2] != name: 197 | if name != '': 198 | yield build_metric(name, documentation, typ, samples) 199 | # New metric 200 | name = parts[2] 201 | documentation = '' 202 | samples = [] 203 | typ = parts[3] 204 | allowed_names = { 205 | 'counter': [''], 206 | 'gauge': [''], 207 | 'summary': ['_count', '_sum', ''], 208 | 'histogram': ['_count', '_sum', '_bucket'], 209 | }.get(typ, ['']) 210 | allowed_names = [name + n for n in allowed_names] 211 | else: 212 | # Ignore other comment tokens 213 | pass 214 | elif line == '': 215 | # Ignore blank lines 216 | pass 217 | else: 218 | sample = _parse_sample(line) 219 | if sample.name not in allowed_names: 220 | if name != '': 221 | yield build_metric(name, documentation, typ, samples) 222 | # New metric, yield immediately as untyped singleton 223 | name = '' 224 | documentation = '' 225 | typ = 'untyped' 226 | samples = [] 227 | allowed_names = [] 228 | yield build_metric(sample[0], documentation, typ, [sample]) 229 | else: 230 | samples.append(sample) 231 | 232 | if name != '': 233 | yield build_metric(name, documentation, typ, samples) 234 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/platform_collector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 3 | from __future__ import unicode_literals 4 | 5 | import platform as pf 6 | 7 | from .metrics_core import GaugeMetricFamily 8 | from .registry import REGISTRY 9 | 10 | 11 | class PlatformCollector(object): 12 | """Collector for python platform information""" 13 | 14 | def __init__(self, registry=REGISTRY, platform=None): 15 | self._platform = pf if platform is None else platform 16 | info = self._info() 17 | system = self._platform.system() 18 | if system == "Java": 19 | info.update(self._java()) 20 | self._metrics = [ 21 | self._add_metric("python_info", "Python platform information", info) 22 | ] 23 | if registry: 24 | registry.register(self) 25 | 26 | def collect(self): 27 | return self._metrics 28 | 29 | @staticmethod 30 | def _add_metric(name, documentation, data): 31 | labels = data.keys() 32 | values = [data[k] for k in labels] 33 | g = GaugeMetricFamily(name, documentation, labels=labels) 34 | g.add_metric(values, 1) 35 | return g 36 | 37 | def _info(self): 38 | major, minor, patchlevel = self._platform.python_version_tuple() 39 | return { 40 | "version": self._platform.python_version(), 41 | "implementation": self._platform.python_implementation(), 42 | "major": major, 43 | "minor": minor, 44 | "patchlevel": patchlevel 45 | } 46 | 47 | def _java(self): 48 | java_version, _, vminfo, osinfo = self._platform.java_ver() 49 | vm_name, vm_release, vm_vendor = vminfo 50 | return { 51 | "jvm_version": java_version, 52 | "jvm_release": vm_release, 53 | "jvm_vendor": vm_vendor, 54 | "jvm_name": vm_name 55 | } 56 | 57 | 58 | PLATFORM_COLLECTOR = PlatformCollector() 59 | """PlatformCollector in default Registry REGISTRY""" 60 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/process_collector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import unicode_literals 4 | 5 | import os 6 | 7 | from .metrics_core import CounterMetricFamily, GaugeMetricFamily 8 | from .registry import REGISTRY 9 | 10 | try: 11 | import resource 12 | _PAGESIZE = resource.getpagesize() 13 | except ImportError: 14 | # Not Unix 15 | _PAGESIZE = 4096 16 | 17 | 18 | class ProcessCollector(object): 19 | """Collector for Standard Exports such as cpu and memory.""" 20 | 21 | def __init__(self, namespace='', pid=lambda: 'self', proc='/proc', registry=REGISTRY): 22 | self._namespace = namespace 23 | self._pid = pid 24 | self._proc = proc 25 | if namespace: 26 | self._prefix = namespace + '_process_' 27 | else: 28 | self._prefix = 'process_' 29 | self._ticks = 100.0 30 | try: 31 | self._ticks = os.sysconf('SC_CLK_TCK') 32 | except (ValueError, TypeError, AttributeError): 33 | pass 34 | 35 | # This is used to test if we can access /proc. 36 | self._btime = 0 37 | try: 38 | self._btime = self._boot_time() 39 | except IOError: 40 | pass 41 | if registry: 42 | registry.register(self) 43 | 44 | def _boot_time(self): 45 | with open(os.path.join(self._proc, 'stat'), 'rb') as stat: 46 | for line in stat: 47 | if line.startswith(b'btime '): 48 | return float(line.split()[1]) 49 | 50 | def collect(self): 51 | if not self._btime: 52 | return [] 53 | 54 | pid = os.path.join(self._proc, str(self._pid()).strip()) 55 | 56 | result = [] 57 | try: 58 | with open(os.path.join(pid, 'stat'), 'rb') as stat: 59 | parts = (stat.read().split(b')')[-1].split()) 60 | 61 | vmem = GaugeMetricFamily(self._prefix + 'virtual_memory_bytes', 62 | 'Virtual memory size in bytes.', value=float(parts[20])) 63 | rss = GaugeMetricFamily(self._prefix + 'resident_memory_bytes', 'Resident memory size in bytes.', 64 | value=float(parts[21]) * _PAGESIZE) 65 | start_time_secs = float(parts[19]) / self._ticks 66 | start_time = GaugeMetricFamily(self._prefix + 'start_time_seconds', 67 | 'Start time of the process since unix epoch in seconds.', 68 | value=start_time_secs + self._btime) 69 | utime = float(parts[11]) / self._ticks 70 | stime = float(parts[12]) / self._ticks 71 | cpu = CounterMetricFamily(self._prefix + 'cpu_seconds_total', 72 | 'Total user and system CPU time spent in seconds.', 73 | value=utime + stime) 74 | result.extend([vmem, rss, start_time, cpu]) 75 | except IOError: 76 | pass 77 | 78 | try: 79 | with open(os.path.join(pid, 'limits'), 'rb') as limits: 80 | for line in limits: 81 | if line.startswith(b'Max open file'): 82 | max_fds = GaugeMetricFamily(self._prefix + 'max_fds', 83 | 'Maximum number of open file descriptors.', 84 | value=float(line.split()[3])) 85 | break 86 | open_fds = GaugeMetricFamily(self._prefix + 'open_fds', 87 | 'Number of open file descriptors.', 88 | len(os.listdir(os.path.join(pid, 'fd')))) 89 | result.extend([open_fds, max_fds]) 90 | except (IOError, OSError): 91 | pass 92 | 93 | return result 94 | 95 | 96 | PROCESS_COLLECTOR = ProcessCollector() 97 | """Default ProcessCollector in default Registry REGISTRY.""" 98 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/registry.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from threading import Lock 3 | 4 | from .metrics_core import Metric 5 | 6 | 7 | class CollectorRegistry(object): 8 | '''Metric collector registry. 9 | 10 | Collectors must have a no-argument method 'collect' that returns a list of 11 | Metric objects. The returned metrics should be consistent with the Prometheus 12 | exposition formats. 13 | ''' 14 | 15 | def __init__(self, auto_describe=False): 16 | self._collector_to_names = {} 17 | self._names_to_collectors = {} 18 | self._auto_describe = auto_describe 19 | self._lock = Lock() 20 | 21 | def register(self, collector): 22 | '''Add a collector to the registry.''' 23 | with self._lock: 24 | names = self._get_names(collector) 25 | duplicates = set(self._names_to_collectors).intersection(names) 26 | if duplicates: 27 | raise ValueError( 28 | 'Duplicated timeseries in CollectorRegistry: {0}'.format( 29 | duplicates)) 30 | for name in names: 31 | self._names_to_collectors[name] = collector 32 | self._collector_to_names[collector] = names 33 | 34 | def unregister(self, collector): 35 | '''Remove a collector from the registry.''' 36 | with self._lock: 37 | for name in self._collector_to_names[collector]: 38 | del self._names_to_collectors[name] 39 | del self._collector_to_names[collector] 40 | 41 | def _get_names(self, collector): 42 | '''Get names of timeseries the collector produces.''' 43 | desc_func = None 44 | # If there's a describe function, use it. 45 | try: 46 | desc_func = collector.describe 47 | except AttributeError: 48 | pass 49 | # Otherwise, if auto describe is enabled use the collect function. 50 | if not desc_func and self._auto_describe: 51 | desc_func = collector.collect 52 | 53 | if not desc_func: 54 | return [] 55 | 56 | result = [] 57 | type_suffixes = { 58 | 'counter': ['_total', '_created'], 59 | 'summary': ['', '_sum', '_count', '_created'], 60 | 'histogram': ['_bucket', '_sum', '_count', '_created'], 61 | 'gaugehistogram': ['_bucket', '_gsum', '_gcount'], 62 | 'info': ['_info'], 63 | } 64 | for metric in desc_func(): 65 | for suffix in type_suffixes.get(metric.type, ['']): 66 | result.append(metric.name + suffix) 67 | return result 68 | 69 | def collect(self): 70 | '''Yields metrics from the collectors in the registry.''' 71 | collectors = None 72 | with self._lock: 73 | collectors = copy.copy(self._collector_to_names) 74 | for collector in collectors: 75 | for metric in collector.collect(): 76 | yield metric 77 | 78 | def restricted_registry(self, names): 79 | '''Returns object that only collects some metrics. 80 | 81 | Returns an object which upon collect() will return 82 | only samples with the given names. 83 | 84 | Intended usage is: 85 | generate_latest(REGISTRY.restricted_registry(['a_timeseries'])) 86 | 87 | Experimental.''' 88 | names = set(names) 89 | collectors = set() 90 | with self._lock: 91 | for name in names: 92 | if name in self._names_to_collectors: 93 | collectors.add(self._names_to_collectors[name]) 94 | metrics = [] 95 | for collector in collectors: 96 | for metric in collector.collect(): 97 | samples = [s for s in metric.samples if s[0] in names] 98 | if samples: 99 | m = Metric(metric.name, metric.documentation, metric.type) 100 | m.samples = samples 101 | metrics.append(m) 102 | 103 | class RestrictedRegistry(object): 104 | def collect(self): 105 | return metrics 106 | 107 | return RestrictedRegistry() 108 | 109 | def get_sample_value(self, name, labels=None): 110 | '''Returns the sample value, or None if not found. 111 | 112 | This is inefficient, and intended only for use in unittests. 113 | ''' 114 | if labels is None: 115 | labels = {} 116 | for metric in self.collect(): 117 | for s in metric.samples: 118 | if s.name == name and s.labels == labels: 119 | return s.value 120 | return None 121 | 122 | 123 | REGISTRY = CollectorRegistry(auto_describe=True) 124 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/samples.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | class Timestamp(object): 5 | '''A nanosecond-resolution timestamp.''' 6 | 7 | def __init__(self, sec, nsec): 8 | if nsec < 0 or nsec >= 1e9: 9 | raise ValueError("Invalid value for nanoseconds in Timestamp: {0}".format(nsec)) 10 | if sec < 0: 11 | nsec = -nsec 12 | self.sec = int(sec) 13 | self.nsec = int(nsec) 14 | 15 | def __str__(self): 16 | return "{0}.{1:09d}".format(self.sec, self.nsec) 17 | 18 | def __repr__(self): 19 | return "Timestamp({0}, {1})".format(self.sec, self.nsec) 20 | 21 | def __float__(self): 22 | return float(self.sec) + float(self.nsec) / 1e9 23 | 24 | def __eq__(self, other): 25 | return type(self) == type(other) and self.sec == other.sec and self.nsec == other.nsec 26 | 27 | def __ne__(self, other): 28 | return not self == other 29 | 30 | def __gt__(self, other): 31 | return self.sec > other.sec or self.nsec > other.nsec 32 | 33 | 34 | # Timestamp and exemplar are optional. 35 | # Value can be an int or a float. 36 | # Timestamp can be a float containing a unixtime in seconds, 37 | # a Timestamp object, or None. 38 | # Exemplar can be an Exemplar object, or None. 39 | Sample = namedtuple('Sample', ['name', 'labels', 'value', 'timestamp', 'exemplar']) 40 | Sample.__new__.__defaults__ = (None, None) 41 | 42 | Exemplar = namedtuple('Exemplar', ['labels', 'value', 'timestamp']) 43 | Exemplar.__new__.__defaults__ = (None,) 44 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/twisted/__init__.py: -------------------------------------------------------------------------------- 1 | from ._exposition import MetricsResource 2 | 3 | __all__ = ['MetricsResource'] 4 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/twisted/_exposition.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | from twisted.web.resource import Resource 4 | 5 | from .. import exposition, REGISTRY 6 | 7 | 8 | class MetricsResource(Resource): 9 | """ 10 | Twisted ``Resource`` that serves prometheus metrics. 11 | """ 12 | isLeaf = True 13 | 14 | def __init__(self, registry=REGISTRY): 15 | self.registry = registry 16 | 17 | def render_GET(self, request): 18 | encoder, content_type = exposition.choose_encoder(request.getHeader('Accept')) 19 | request.setHeader(b'Content-Type', content_type.encode('ascii')) 20 | return encoder(self.registry) 21 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | INF = float("inf") 4 | MINUS_INF = float("-inf") 5 | 6 | 7 | def floatToGoString(d): 8 | d = float(d) 9 | if d == INF: 10 | return '+Inf' 11 | elif d == MINUS_INF: 12 | return '-Inf' 13 | elif math.isnan(d): 14 | return 'NaN' 15 | else: 16 | s = repr(d) 17 | dot = s.find('.') 18 | # Go switches to exponents sooner than Python. 19 | # We only need to care about positive values for le/quantile. 20 | if d > 0 and dot > 6: 21 | mantissa = '{0}.{1}{2}'.format(s[0], s[1:dot], s[dot+1:]).rstrip('0.') 22 | return '{0}e+0{1}'.format(mantissa, dot-1) 23 | return s 24 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/prometheus_client/values.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import os 4 | from threading import Lock 5 | 6 | from .mmap_dict import mmap_key, MmapedDict 7 | 8 | 9 | class MutexValue(object): 10 | '''A float protected by a mutex.''' 11 | 12 | _multiprocess = False 13 | 14 | def __init__(self, typ, metric_name, name, labelnames, labelvalues, **kwargs): 15 | self._value = 0.0 16 | self._lock = Lock() 17 | 18 | def inc(self, amount): 19 | with self._lock: 20 | self._value += amount 21 | 22 | def set(self, value): 23 | with self._lock: 24 | self._value = value 25 | 26 | def get(self): 27 | with self._lock: 28 | return self._value 29 | 30 | 31 | def MultiProcessValue(_pidFunc=os.getpid): 32 | files = {} 33 | values = [] 34 | pid = {'value': _pidFunc()} 35 | # Use a single global lock when in multi-processing mode 36 | # as we presume this means there is no threading going on. 37 | # This avoids the need to also have mutexes in __MmapDict. 38 | lock = Lock() 39 | 40 | class MmapedValue(object): 41 | '''A float protected by a mutex backed by a per-process mmaped file.''' 42 | 43 | _multiprocess = True 44 | 45 | def __init__(self, typ, metric_name, name, labelnames, labelvalues, multiprocess_mode='', **kwargs): 46 | self._params = typ, metric_name, name, labelnames, labelvalues, multiprocess_mode 47 | with lock: 48 | self.__check_for_pid_change() 49 | self.__reset() 50 | values.append(self) 51 | 52 | def __reset(self): 53 | typ, metric_name, name, labelnames, labelvalues, multiprocess_mode = self._params 54 | if typ == 'gauge': 55 | file_prefix = typ + '_' + multiprocess_mode 56 | else: 57 | file_prefix = typ 58 | if file_prefix not in files: 59 | filename = os.path.join( 60 | os.environ['prometheus_multiproc_dir'], 61 | '{0}_{1}.db'.format(file_prefix, pid['value'])) 62 | 63 | files[file_prefix] = MmapedDict(filename) 64 | self._file = files[file_prefix] 65 | self._key = mmap_key(metric_name, name, labelnames, labelvalues) 66 | self._value = self._file.read_value(self._key) 67 | 68 | def __check_for_pid_change(self): 69 | actual_pid = _pidFunc() 70 | if pid['value'] != actual_pid: 71 | pid['value'] = actual_pid 72 | # There has been a fork(), reset all the values. 73 | for f in files.values(): 74 | f.close() 75 | files.clear() 76 | for value in values: 77 | value.__reset() 78 | 79 | def inc(self, amount): 80 | with lock: 81 | self.__check_for_pid_change() 82 | self._value += amount 83 | self._file.write_value(self._key, self._value) 84 | 85 | def set(self, value): 86 | with lock: 87 | self.__check_for_pid_change() 88 | self._value = value 89 | self._file.write_value(self._key, self._value) 90 | 91 | def get(self): 92 | with lock: 93 | self.__check_for_pid_change() 94 | return self._value 95 | 96 | return MmapedValue 97 | 98 | 99 | def get_value_class(): 100 | # Should we enable multi-process mode? 101 | # This needs to be chosen before the first metric is constructed, 102 | # and as that may be in some arbitrary library the user/admin has 103 | # no control over we use an environment variable. 104 | if 'prometheus_multiproc_dir' in os.environ: 105 | return MultiProcessValue() 106 | else: 107 | return MutexValue 108 | 109 | 110 | ValueClass = get_value_class() 111 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | 5 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/packages/prometheus_client-0.6.0/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="prometheus_client", 5 | version="0.6.0", 6 | author="Brian Brazil", 7 | author_email="brian.brazil@robustperception.io", 8 | description="Python client for the Prometheus monitoring system.", 9 | long_description=( 10 | "See https://github.com/prometheus/client_python/blob/master/README.md" 11 | " for documentation."), 12 | license="Apache Software License 2.0", 13 | keywords="prometheus monitoring instrumentation client", 14 | url="https://github.com/prometheus/client_python", 15 | packages=[ 16 | 'prometheus_client', 17 | 'prometheus_client.bridge', 18 | 'prometheus_client.openmetrics', 19 | 'prometheus_client.twisted', 20 | ], 21 | extras_require={ 22 | 'twisted': ['twisted'], 23 | }, 24 | test_suite="tests", 25 | classifiers=[ 26 | "Development Status :: 4 - Beta", 27 | "Intended Audience :: Developers", 28 | "Intended Audience :: Information Technology", 29 | "Intended Audience :: System Administrators", 30 | "Programming Language :: Python", 31 | "Programming Language :: Python :: 2", 32 | "Programming Language :: Python :: 2.6", 33 | "Programming Language :: Python :: 2.7", 34 | "Programming Language :: Python :: 3", 35 | "Programming Language :: Python :: 3.4", 36 | "Programming Language :: Python :: 3.5", 37 | "Programming Language :: Python :: 3.6", 38 | "Programming Language :: Python :: Implementation :: CPython", 39 | "Programming Language :: Python :: Implementation :: PyPy", 40 | "Topic :: System :: Monitoring", 41 | "License :: OSI Approved :: Apache Software License", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /test/workspaces/pip-app/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.2 2 | Django==1.6.1 3 | python-etcd==0.4.5 4 | urllib3==1.26.16 5 | Django-Select2==6.0.1 # this version installs with lowercase so it catches a previous bug in pip_resolve.py 6 | irc==16.2 # this has a cyclic dependency (internal jaraco.text <==> jaraco.collections) 7 | testtools==\ 8 | 2.3.0 # this has a cycle (fixtures ==> testtools); 9 | ./packages/prometheus_client-0.6.0 10 | opentelemetry-distro[otlp] == 0.35b0 11 | jsonschema==4.23.0 12 | -------------------------------------------------------------------------------- /test/workspaces/pipenv-app/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | python-etcd = ">=0.4,<0.5" 8 | urllib3 = "1.26.16" 9 | testtools = "*" 10 | "Jinja2" = { version = "*" } 11 | 12 | [dev-packages] 13 | bs4 = "*" 14 | 15 | [requires] 16 | -------------------------------------------------------------------------------- /test/workspaces/pipenv-app/README: -------------------------------------------------------------------------------- 1 | This is a small pipenv-based config with a variety of types of dependency 2 | defintions, based on pip-app and the pipenv example files. 3 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-empty/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | 10 | [requires] 11 | python_version = "2.7" 12 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-markers/Pipfile: -------------------------------------------------------------------------------- 1 | [dev-packages] 2 | "flake8" = ">=3.3.0,<4" 3 | sphinx = "<=1.5.5" 4 | twine = "*" 5 | sphinx-click = "*" 6 | click = "*" 7 | pytest_pypi = {path = "./tests/pytest-pypi", editable = true} 8 | stdeb = {version="*", markers="sys_platform == 'linux' ; python_version != '3.4"} 9 | black = {version="*", markers="python_version > '3.6'"} 10 | pytz = "*" 11 | towncrier = {git = "https://github.com/hawkowl/towncrier.git", editable = true, ref = "master"} 12 | parver = "*" 13 | invoke = "*" 14 | jedi = "*" 15 | isort = "*" 16 | rope = "*" 17 | passa = {editable = true, git = "https://github.com/sarugaku/passa.git"} 18 | bs4 = "*" 19 | 20 | [packages] 21 | 22 | [scripts] 23 | tests = "bash ./run-tests.sh" 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-nested-dirs/README: -------------------------------------------------------------------------------- 1 | This is a small pipenv-based config with a deeply located Pipfile. 2 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-nested-dirs/nested/directory/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | python-etcd = ">=0.4,<0.5" 8 | urllib3 = "1.26.16" 9 | testtools = "*" 10 | "Jinja2" = { version = "*" } 11 | django = { git = 'https://github.com/django/django.git', ref = '1.6.1', editable = true } 12 | "Django-Select2" = { version = "==6.0.1" } 13 | "e1839a8" = {path = ".", editable = true} 14 | 15 | [dev-packages] 16 | 17 | [requires] 18 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-optional-dependencies/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 'opentelemetry-distro[otlp]' = '==0.35b0' 8 | 9 | [pipenv] 10 | allow_prereleases = true 11 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-pipapp-pinned/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | python-etcd = "==0.4.5" 8 | urllib3 = "==1.26.16" 9 | irc = "==16.2" 10 | testtools = "==2.3.0" 11 | "Jinja2" = "==2.7.2" 12 | Django = "==1.6.1" 13 | "Django-Select2" = "==6.0.1" 14 | 15 | [dev-packages] 16 | 17 | [requires] 18 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-pipapp-pinned/README: -------------------------------------------------------------------------------- 1 | This is a small pipenv-based config with all dependency versions pinned 2 | in the Pipfile, based on pip-app. 3 | 4 | This was created by running the following commands in a clean python3.6 5 | virtualenv: 6 | 7 | pip install pipenv 8 | pipenv install -r test/workspaces/pip-app/requirements.txt 9 | # manually empty the "requires" section of the Pipfile 10 | pipenv lock 11 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-pipapp/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | python-etcd = ">=0.4,<0.5" 8 | urllib3 = "==1.26.16" 9 | testtools = "*" 10 | "Jinja2" = { version = "*" } 11 | django = { git = 'https://github.com/django/django.git', ref = '1.6.1', editable = true } 12 | "Django-Select2" = { version = "==6.0.1" } 13 | "e1839a8" = {path = ".", editable = true} 14 | 15 | [dev-packages] 16 | 17 | [requires] 18 | -------------------------------------------------------------------------------- /test/workspaces/pipfile-pipapp/README: -------------------------------------------------------------------------------- 1 | This is a small pipenv-based config with a variety of types of dependency 2 | definitions, based on pip-app and the pipenv example files. 3 | -------------------------------------------------------------------------------- /test/workspaces/poetry-app-optional-dependencies/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "extras-poetry" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["MarcusArdelean "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "3.12.1" 10 | opentelemetry-distro = {version = "0.35b0", extras = ["otlp"]} 11 | 12 | [build-system] 13 | requires = ["poetry-core"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /test/workspaces/poetry-app-without-lockfile/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry-fixtures-project" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = "~2.7 || ^3.5" 9 | jinja2 = "^2.11" 10 | 11 | [build-system] 12 | requires = ["poetry>=0.12"] 13 | build-backend = "poetry.masonry.api" -------------------------------------------------------------------------------- /test/workspaces/poetry-app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry-fixtures-project" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = "~2.7 || ^3.5" 9 | Jinja2 = "^2.11" 10 | 11 | [build-system] 12 | requires = ["poetry>=0.12"] 13 | build-backend = "poetry.masonry.api" -------------------------------------------------------------------------------- /test/workspaces/poetry-v2-app-optional-dependencies/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dep-with-optional-dependency" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [ 6 | {name = "Your Name",email = "you@example.com"} 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | dependencies = [ 11 | "opentelemetry-distro[otlp] (==0.35b0)", 12 | ] 13 | 14 | [build-system] 15 | requires = ["poetry-core>=3.6"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /test/workspaces/poetry-v2-app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "poetry-fixtures-project" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [ 6 | {name = "Your Name",email = "you@example.com"} 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | dependencies = [ 11 | "jinja2 (>=3.1.2)", 12 | "isOdd (>=0.1.2)", 13 | ] 14 | [build-system] 15 | requires = ["poetry-core>=3.6"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /test/workspaces/setup_py-app-optional-dependencies/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name="test_package", 5 | version="1.0.2", 6 | install_requires=[ 7 | "opentelemetry-distro[otlp] == 0.35b0" 8 | ], 9 | ) -------------------------------------------------------------------------------- /test/workspaces/setup_py-app/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name="test_package", 7 | version="1.0.2", 8 | install_requires=[ 9 | "Django==1.6.1", 10 | "python-etcd==0.4.5", 11 | "urllib3==1.26.16", 12 | "Django-Select2==6.0.1", 13 | "irc==16.2", 14 | "testtools==2.3.0", 15 | "jsonschema==4.23.0" 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py36 3 | 4 | [testenv] 5 | passenv = APPDATA 6 | deps = -rdev-requirements.txt 7 | whitelist_externals = npm 8 | commands = 9 | npm i 10 | npm run test 11 | -------------------------------------------------------------------------------- /tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build-with-tests", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | }, 8 | "include": [ 9 | "./lib/**/*", 10 | "./test/**/*", 11 | "./test/matchers/*.ts", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "pretty": true, 5 | "target": "es2015", 6 | "lib": [ 7 | "es2015", 8 | ], 9 | "module": "commonjs", 10 | "allowJs": false, 11 | "sourceMap": true, 12 | "strict": false, 13 | "declaration": true, 14 | "importHelpers": true, 15 | }, 16 | "include": [ 17 | "./lib/**/*" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------