Commit f797e172 by Sébastien Eustace Committed by GitHub

Merge pull request #1867 from python-poetry/merge-master-into-develop

Merge master into develop
parents 39c57cc3 4f3d3d00
...@@ -16,8 +16,8 @@ assignees: '' ...@@ -16,8 +16,8 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version. - [ ] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option). - [ ] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).
<!-- <!--
......
--- ---
name: "\U0001F4DA Documentation" name: "\U0001F4DA Documentation"
about: Did you find errors, problems, or anything unintelligible in the docs (https://poetry.eustace.io/docs)? about: Did you find errors, problems, or anything unintelligible in the docs (https://python-poetry.org/docs)?
title: '' title: ''
labels: 'Documentation' labels: 'Documentation'
assignees: '' assignees: ''
...@@ -16,7 +16,7 @@ assignees: '' ...@@ -16,7 +16,7 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
## Issue ## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ --> <!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
...@@ -13,8 +13,8 @@ assignees: '' ...@@ -13,8 +13,8 @@ assignees: ''
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://poetry.eustace.io/docs/) and believe that my question is not covered. - [ ] I have searched the [documentation](https://python-poetry.org/docs/) and believe that my question is not covered.
## Issue ## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ --> <!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
...@@ -16,8 +16,8 @@ assignees: '' ...@@ -16,8 +16,8 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://poetry.eustace.io/docs/) and believe that my question is not covered. - [ ] I have searched the [documentation](https://python-poetry.org/docs/) and believe that my question is not covered.
## Feature Request ## Feature Request
<!-- Now feel free to write your idea for improvement. Thanks again 🙌 ❤️ --> <!-- Now feel free to write your idea for improvement. Thanks again 🙌 ❤️ -->
# Pull Request Check List # Pull Request Check List
This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](https://poetry.eustace.io/docs/contributing/) at least once, it will save you unnecessary review cycles! This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](https://python-poetry.org/docs/contributing/) at least once, it will save you unnecessary review cycles!
- [ ] Added **tests** for changed code. - [ ] Added **tests** for changed code.
- [ ] Updated **documentation** for changed code. - [ ] Updated **documentation** for changed code.
......
exemptProjects: true
exemptMilestones: true
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: >
Closing this issue automatically because it has not had any activity since
it has been marked as stale. If you think it is still relevant and should
be addressed, feel free to open a new one.
pulls:
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: >
Closing this pull request automatically because it has not had any activity since
it has been marked as stale. If you think it is still relevant and should
be addressed, feel free to open a new one.
...@@ -22,7 +22,7 @@ jobs: ...@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -30,10 +30,16 @@ jobs: ...@@ -30,10 +30,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and set up Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
source $HOME/.poetry/env source $HOME/.poetry/env
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
source $HOME/.poetry/env source $HOME/.poetry/env
...@@ -48,7 +54,7 @@ jobs: ...@@ -48,7 +54,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -56,10 +62,16 @@ jobs: ...@@ -56,10 +62,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and set up Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
source $HOME/.poetry/env source $HOME/.poetry/env
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
source $HOME/.poetry/env source $HOME/.poetry/env
...@@ -74,7 +86,7 @@ jobs: ...@@ -74,7 +86,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -82,10 +94,16 @@ jobs: ...@@ -82,10 +94,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and setup Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
$env:Path += ";$env:Userprofile\.poetry\bin" $env:Path += ";$env:Userprofile\.poetry\bin"
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
$env:Path += ";$env:Userprofile\.poetry\bin" $env:Path += ";$env:Userprofile\.poetry\bin"
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
# Packages # Packages
*.egg *.egg
*.egg-info !/tests/**/*.egg
/*.egg-info
/tests/fixtures/**/*.egg-info
/dist/* /dist/*
build build
_build _build
......
...@@ -20,5 +20,7 @@ repos: ...@@ -20,5 +20,7 @@ repos:
rev: v2.3.0 rev: v2.3.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^tests/.*/fixtures/.*
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: ^tests/.*/fixtures/.*
- id: debug-statements - id: debug-statements
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sebastien@eustace.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
...@@ -20,18 +20,18 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These ...@@ -20,18 +20,18 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These
This section guides you through submitting a bug report for Poetry. This section guides you through submitting a bug report for Poetry.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster. Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before submitting a bug report #### Before submitting a bug report
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems. * **Check the [FAQs on the official website](https://python-poetry.org)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**. * **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**.
#### How do I submit a bug report? #### How do I submit a bug report?
Bugs are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md). Bugs are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem: Explain the problem and include additional details to help maintainers reproduce the problem:
...@@ -60,17 +60,17 @@ Include details about your configuration and environment: ...@@ -60,17 +60,17 @@ Include details about your configuration and environment:
This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed. Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before submitting an enhancement suggestion #### Before submitting an enhancement suggestion
* **Check the [FAQs on the official website](https://poetry.eustace.io) for a list of common questions and problems. * **Check the [FAQs on the official website](https://python-poetry.org) for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues). * **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues).
#### How do I submit an Enhancement suggestion? #### How do I submit an Enhancement suggestion?
Enhancement suggestions are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information: Enhancement suggestions are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion. * **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. * **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
...@@ -82,12 +82,12 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith ...@@ -82,12 +82,12 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith
#### Local development #### Local development
You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://poetry.eustace.io/docs/#introduction) to start using Poetry. You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://python-poetry.org/docs/#introduction) to start using Poetry.
You will first need to clone the repository using `git` and place yourself in its directory: You will first need to clone the repository using `git` and place yourself in its directory:
```bash ```bash
$ git clone git@github.com:sdispater/poetry.git $ git clone git@github.com:python-poetry/poetry.git
$ cd poetry $ cd poetry
``` ```
...@@ -123,6 +123,6 @@ will not be merged. ...@@ -123,6 +123,6 @@ will not be merged.
#### Pull requests #### Pull requests
* Fill in [the required template](https://github.com/sdispater/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md) * Fill in [the required template](https://github.com/python-poetry/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that your pull request contains tests that cover the changed or added code. * Be sure that your pull request contains tests that cover the changed or added code.
* If your changes warrant a documentation change, the pull request must also update the documentation. * If your changes warrant a documentation change, the pull request must also update the documentation.
# This file is part of Poetry # This file is part of Poetry
# https://github.com/sdispater/poetry # https://github.com/python-poetry/poetry
# Licensed under the MIT license: # Licensed under the MIT license:
# http://www.opensource.org/licenses/MIT-license # http://www.opensource.org/licenses/MIT-license
...@@ -48,7 +48,7 @@ wheel: ...@@ -48,7 +48,7 @@ wheel:
linux_release: linux_release:
docker pull quay.io/pypa/manylinux2010_x86_64 docker pull quay.io/pypa/manylinux2010_x86_64
docker run --rm -t -i -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/make-linux-release.sh docker run --rm -i -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/make-linux-release.sh
# run tests against all supported python versions # run tests against all supported python versions
tox: tox:
......
...@@ -69,7 +69,8 @@ It will automatically find a suitable version constraint **and install** the pac ...@@ -69,7 +69,8 @@ It will automatically find a suitable version constraint **and install** the pac
In our example, we are requesting the `pendulum` package with the version constraint `^1.4`. In our example, we are requesting the `pendulum` package with the version constraint `^1.4`.
This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`). This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`).
Please read [versions](/docs/versions/) for more in-depth information on versions, how versions relate to each other, and on version constraints. Please read [Dependency specification](/docs/dependency-specification) for more in-depth information on versions,
how versions relate to each other, and on the different ways you can specify dependencies.
!!!note !!!note
...@@ -147,107 +148,3 @@ and update the lock file with the new versions. ...@@ -147,107 +148,3 @@ and update the lock file with the new versions.
Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml`
are not synchronized. are not synchronized.
## Poetry and virtualenvs
When you execute the `install` command (or any other "install" commands like `add` or `remove`),
Poetry will check if it's currently inside a virtualenv and, if not, will use an existing one
or create a brand new one for you to always work isolated from your global Python installation.
By default, Poetry will use the currently activated Python version
to create the virtualenv for the current project.
To easily switch between Python versions, it is recommended to
use [pyenv](https://github.com/pyenv/pyenv) or similar tools.
For instance, if your project is Python 2.7 only, a standard workflow
would be:
```bash
pyenv install 2.7.15
pyenv local 2.7.15 # Activate Python 2.7 for the current project
poetry install
```
However, this might not be feasible for your system, especially Windows where `pyenv`,
is not available. To circumvent that you can use the `env use` command to tell
Poetry which Python version to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtualenv, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
If you want to get basic information about the currently activated virtualenv,
you can use the `env info` command:
```bash
poetry env info
```
will output something similar to this:
```text
Virtualenv
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtualenv, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
You can also list all the virtualenvs associated with the current virtualenv
with the `env list` command:
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
Finally, you can delete existing virtualenvs by using `env remove`:
```bash
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
If your remove the currently activated virtualenv, it will be automatically deactivated.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
You've already learned how to use the command-line interface to do some things. You've already learned how to use the command-line interface to do some things.
This chapter documents all the available commands. This chapter documents all the available commands.
To get help from the command-line, simply call `poetry` or `poetry list` to see the complete list of commands, To get help from the command-line, simply call `poetry` to see the complete list of commands,
then `--help` combined with any of those can give you more information. then `--help` combined with any of those can give you more information.
As `Poetry` uses [cleo](https://github.com/sdispater/cleo) you can call commands by short name if it's not ambiguous. As `Poetry` uses [cleo](https://github.com/sdispater/cleo) you can call commands by short name if it's not ambiguous.
...@@ -130,7 +130,7 @@ By default `poetry` will install your project's package everytime you run `insta ...@@ -130,7 +130,7 @@ By default `poetry` will install your project's package everytime you run `insta
$ poetry install $ poetry install
Installing dependencies from lock file Installing dependencies from lock file
Nothing to install or update No dependencies to install or update
- Installing <your-package-name> (x.x.x) - Installing <your-package-name> (x.x.x)
...@@ -208,8 +208,8 @@ If you need to checkout a specific branch, tag or revision, ...@@ -208,8 +208,8 @@ If you need to checkout a specific branch, tag or revision,
you can specify it when using `add`: you can specify it when using `add`:
```bash ```bash
poetry add git+https://github.com/sdispater/pendulum.git@develop poetry add git+https://github.com/sdispater/pendulum.git#develop
poetry add git+https://github.com/sdispater/pendulum.git@2.0.5 poetry add git+https://github.com/sdispater/pendulum.git#2.0.5
``` ```
or make them point to a local directory or file: or make them point to a local directory or file:
...@@ -451,106 +451,4 @@ poetry export -f requirements.txt > requirements.txt ...@@ -451,106 +451,4 @@ poetry export -f requirements.txt > requirements.txt
The `env` command regroups sub commands to interact with the virtualenvs The `env` command regroups sub commands to interact with the virtualenvs
associated with a specific project. associated with a specific project.
### env use See [Managing environments](./managing-environments.md) for more information about these commands.
The `env use` command tells Poetry which Python version
to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtualenv, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
### env info
The `env info` command displays basic information about the currently activated virtualenv:
```bash
poetry env info
```
will output something similar to this:
```text
Virtualenv
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtualenv, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
#### Options
* `--path`: Only display the path of the virtualenv.
### env list
The `env list` command lists all the virtualenvs associated with the current virtualenv.
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
#### Options
* `--full-path`: Display the full path of the virtualenvs.
### env remove
The `env remove` command deletes virtualenvs associated with the current project:
```bash
poetry env remove /full/path/to/python
```
Similarly to `env use`, you can either pass `python3.7`, `3.7` or the name of
the virtualenv (as returned by `env list`):
```bash
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
!!!note
If your remove the currently activated virtualenv, it will be automatically deactivated.
...@@ -100,11 +100,11 @@ Defaults to one of the following directories: ...@@ -100,11 +100,11 @@ Defaults to one of the following directories:
- macOS: `~/Library/Caches/pypoetry` - macOS: `~/Library/Caches/pypoetry`
- Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache` - Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache`
- Unix: `~/.cache/pypoetry/virtualenvs` - Unix: `~/.cache/pypoetry`
### `virtualenvs.create`: boolean ### `virtualenvs.create`: boolean
Create a new virtualenv if one doesn't already exist. Create a new virtual environment if one doesn't already exist.
Defaults to `true`. Defaults to `true`.
### `virtualenvs.in-project`: boolean ### `virtualenvs.in-project`: boolean
...@@ -114,7 +114,7 @@ Defaults to `false`. ...@@ -114,7 +114,7 @@ Defaults to `false`.
### `virtualenvs.path`: string ### `virtualenvs.path`: string
Directory where virtualenvs will be created. Directory where virtual environments will be created.
Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows).
### `repositories.<name>`: string ### `repositories.<name>`: string
......
# Contributing to Poetry {!../CONTRIBUTING.md!}
First off, thank for taking the time to contribute!
The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table of Contents
[How to contribute](#how-to-contribute)
* [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements)
* [Contributing to code](#contributing-to-code)
## How to contribute
### Reporting bugs
This section guides you through submitting a bug report for Poetry.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster.
!!!note
If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before submitting a bug report
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**.
#### How do I submit a bug report
Bugs are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible.
* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories).
* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option).
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of Poetry) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Poetry?** What's the most recent version in which the problem doesn't happen?
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
Include details about your configuration and environment:
* **Which version of Poetry are you using?** You can get the exact version by running `poetry -V` in your terminal.
* **Which Python version Poetry has been installed for?** Execute the `debug:info` to get the information.
* **What's the name and version of the OS you're using**?
### Suggesting enhancements
This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before submitting an enhancement suggestion
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**.
#### How do I submit an enhancement suggestion?
Enhancement suggestions are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**..
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
### Contributing to code
#### Local development
You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://poetry.eustace.io/docs/#introduction) to start using Poetry.
You will first need to clone the repository using `git` and place yourself in its directory:
```bash
$ git clone git@github.com:sdispater/poetry.git
$ cd poetry
```
Now, you will need to install the required dependency for Poetry and be sure that the current
tests are passing on your machine:
```bash
$ poetry install
$ poetry run pytest tests/
```
Poetry uses the [black](https://github.com/ambv/black) coding style and you must ensure that your
code follows it. If not, the CI will fail and your Pull Request will not be merged.
Similarly, the import statements are sorted with [isort](https://github.com/timothycrosley/isort)
and special care must be taken to respect it. If you don't, the CI will fail as well.
To make sure that you don't accidentally commit code that does not follow the coding style, you can
install a pre-commit hook that will check that everything is in order:
```bash
$ poetry run pre-commit install
```
You can also run it anytime using:
```bash
$ poetry run pre-commit run --all-files
```
Your code must always be accompanied by corresponding tests, if tests are not present your code
will not be merged.
#### Pull requests
* Fill in [the required template](https://github.com/sdispater/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that you pull request contains tests that cover the changed or added code.
* If you changes warrant a documentation change, the pull request must also update the documentation.
# Versions and constraints # Dependency specification
Poetry recommends following [semantic versioning](https://semver.org) but will not enforce it. Dependencies for a project can be specified in various forms, which depend on the type
of the dependency and on the optional constraints that might be needed for it to be installed.
## Version constraints ## Version constraints
...@@ -74,7 +75,7 @@ If other dependencies require a different version, the solver will ultimately fa ...@@ -74,7 +75,7 @@ If other dependencies require a different version, the solver will ultimately fa
Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`. Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`.
### `git` dependencies ## `git` dependencies
To depend on a library located in a `git` repository, To depend on a library located in a `git` repository,
the minimum information you need to specify is the location of the repository with the git key: the minimum information you need to specify is the location of the repository with the git key:
...@@ -85,16 +86,24 @@ requests = { git = "https://github.com/requests/requests.git" } ...@@ -85,16 +86,24 @@ requests = { git = "https://github.com/requests/requests.git" }
``` ```
Since we haven’t specified any other information, Since we haven’t specified any other information,
Poetry assumes that we intend to use the latest commit on the `master` branch to build our project. Poetry assumes that we intend to use the latest commit on the `master` branch
You can combine the `git` key with the `rev`, `tag`, or `branch` keys to specify something else. to build our project.
Here's an example of specifying that you want to use the latest commit on a branch named `next`:
You can combine the `git` key with the `branch` key to use another branch.
Alternatively, use `rev` or `tag` to pin a dependency to a specific commit hash
or tagged ref, respectively. For example:
```toml ```toml
[tool.poetry.dependencies] [tool.poetry.dependencies]
# Get the latest revision on the branch named "next"
requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" } requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" }
# Get a revision by its commit hash
flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" }
# Get a revision by its tag
numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" }
``` ```
### `path` dependencies ## `path` dependencies
To depend on a library located in a local directory or file, To depend on a library located in a local directory or file,
you can use the `path` property: you can use the `path` property:
...@@ -109,7 +118,7 @@ my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" } ...@@ -109,7 +118,7 @@ my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" }
``` ```
### `url` dependencies ## `url` dependencies
To depend on a library located on a remote archive, To depend on a library located on a remote archive,
you can use the `url` property: you can use the `url` property:
...@@ -127,7 +136,7 @@ poetry add https://example.com/my-package-0.1.0.tar.gz ...@@ -127,7 +136,7 @@ poetry add https://example.com/my-package-0.1.0.tar.gz
``` ```
### Python restricted dependencies ## Python restricted dependencies
You can also specify that a dependency should be installed only for specific Python versions: You can also specify that a dependency should be installed only for specific Python versions:
...@@ -141,7 +150,7 @@ pathlib2 = { version = "^2.2", python = "~2.7" } ...@@ -141,7 +150,7 @@ pathlib2 = { version = "^2.2", python = "~2.7" }
pathlib2 = { version = "^2.2", python = "~2.7 || ^3.2" } pathlib2 = { version = "^2.2", python = "~2.7 || ^3.2" }
``` ```
### Using environment markers ## Using environment markers
If you need more complex install conditions for your dependencies, If you need more complex install conditions for your dependencies,
Poetry supports [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers) Poetry supports [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers)
...@@ -153,7 +162,7 @@ pathlib2 = { version = "^2.2", markers = "python_version ~= '2.7' or sys_platfor ...@@ -153,7 +162,7 @@ pathlib2 = { version = "^2.2", markers = "python_version ~= '2.7' or sys_platfor
``` ```
### Multiple constraints dependencies ## Multiple constraints dependencies
Sometimes, one of your dependency may have different version ranges depending Sometimes, one of your dependency may have different version ranges depending
on the target Python versions. on the target Python versions.
......
...@@ -60,14 +60,14 @@ commands = ...@@ -60,14 +60,14 @@ commands =
poetry run pytest tests/ poetry run pytest tests/
``` ```
## I don't want Poetry to manage my virtualenvs. Can I disable it? ## I don't want Poetry to manage my virtual environments. Can I disable it?
While Poetry automatically creates virtualenvs to always work isolated While Poetry automatically creates virtual environments to always work isolated
from the global Python installation, there are valid reasons why it's not necessary from the global Python installation, there are valid reasons why it's not necessary
and is an overhead, like when working with containers. and is an overhead, like when working with containers.
In this case, you can disable this feature by setting the `virtualenvs.create` setting to `false`: In this case, you can disable this feature by setting the `virtualenvs.create` setting to `false`:
```bash ```bash
poetry config settings.virtualenvs.create false poetry config virtualenvs.create false
``` ```
...@@ -16,8 +16,13 @@ Poetry provides a custom installer that will install `poetry` isolated ...@@ -16,8 +16,13 @@ Poetry provides a custom installer that will install `poetry` isolated
from the rest of your system by vendorizing its dependencies. This is the from the rest of your system by vendorizing its dependencies. This is the
recommended way of installing `poetry`. recommended way of installing `poetry`.
### osx / linux / bashonwindows install instructions
```bash ```bash
curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
```
### windows powershell install instructions
```powershell
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python
``` ```
!!! note !!! note
...@@ -141,6 +146,10 @@ poetry self update 0.8.0 ...@@ -141,6 +146,10 @@ poetry self update 0.8.0
The `self update` command will only work if you used the recommended The `self update` command will only work if you used the recommended
installer to install Poetry. installer to install Poetry.
!!!note
If you are still on poetry version < 1.0 use `poetry self:update` instead.
## Enable tab completion for Bash, Fish, or Zsh ## Enable tab completion for Bash, Fish, or Zsh
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
This chapter will tell you how to make your library installable through Poetry. This chapter will tell you how to make your library installable through Poetry.
## Every project is a package
As soon as you have a `pyproject.toml` in a directory, that directory is a package.
However, to make it accessible to others you will need to package and publish it.
## Versioning ## Versioning
...@@ -14,7 +9,7 @@ While Poetry does not enforce any convention regarding package versioning, ...@@ -14,7 +9,7 @@ While Poetry does not enforce any convention regarding package versioning,
it **strongly** recommends to follow [semantic versioning](https://semver.org). it **strongly** recommends to follow [semantic versioning](https://semver.org).
This has many advantages for the end users and allows them to set appropriate This has many advantages for the end users and allows them to set appropriate
[version constraints](/docs/versions/). [version constraints](/docs/dependency-specification/#version-constraints).
## Lock file ## Lock file
......
# Managing environments
Poetry makes project environment isolation one of its core feature.
What this means is that it will always work isolated from your global Python installation.
To achieve this, it will first check if it's currently running inside a virtual environment.
If it is, it will use it directly without creating a new one. But if it's not, it will use
one that it has already created or create a brand new one for you.
By default, Poetry will try to use the currently activated Python version
to create the virtual environment for the current project.
However, for various reasons, this Python version might not be compatible
with the `python` requirement of the project. In this case, Poetry will try
to find one that is and use it. If it's unable to do so then you will be prompted
to activate one explicitly, see [Switching environments](#switching-between-environments).
!!!note
To easily switch between Python versions, it is recommended to
use [pyenv](https://github.com/pyenv/pyenv) or similar tools.
For instance, if your project is Python 2.7 only, a standard workflow
would be:
```bash
pyenv install 2.7.15
pyenv local 2.7.15 # Activate Python 2.7 for the current project
poetry install
```
## Switching between environments
Sometimes this might not be feasible for your system, especially Windows where `pyenv`
is not available, or you simply prefer to have a more explicit control over your environment.
For this specific purpose, you can use the `env use` command to tell Poetry
which Python version to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtual environment, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
## Displaying the environment information
If you want to get basic information about the currently activated virtual environment,
you can use the `env info` command:
```bash
poetry env info
```
will output something similar to this:
```text
Virtual environment
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtual environment, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
## Listing the environments associated with the project
You can also list all the virtual environments associated with the current virtual environment
with the `env list` command:
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
## Deleting the environments
Finally, you can delete existing virtual environments by using `env remove`:
```bash
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
If your remove the currently activated virtual environment, it will be automatically deactivated.
...@@ -27,14 +27,14 @@ The recommended notation for the most common licenses is (alphabetical): ...@@ -27,14 +27,14 @@ The recommended notation for the most common licenses is (alphabetical):
* BSD-2-Clause * BSD-2-Clause
* BSD-3-Clause * BSD-3-Clause
* BSD-4-Clause * BSD-4-Clause
* GPL-2.0 * GPL-2.0-only
* GPL-2.0+ * GPL-2.0-or-later
* GPL-3.0 * GPL-3.0-only
* GPL-3.0+ * GPL-3.0-or-later
* LGPL-2.1 * LGPL-2.1-only
* LGPL-2.1+ * LGPL-2.1-or-later
* LGPL-3.0 * LGPL-3.0-only
* LGPL-3.0+ * LGPL-3.0-or-later
* MIT * MIT
Optional, but it is highly recommended to supply this. Optional, but it is highly recommended to supply this.
...@@ -264,7 +264,7 @@ any custom url in the `urls` section. ...@@ -264,7 +264,7 @@ any custom url in the `urls` section.
```toml ```toml
[tool.poetry.urls] [tool.poetry.urls]
"Bug Tracker" = "https://github.com/sdispater/poetry/issues" "Bug Tracker" = "https://github.com/python-poetry/poetry/issues"
``` ```
If you publish you package on PyPI, they will appear in the `Project Links` section. If you publish you package on PyPI, they will appear in the `Project Links` section.
......
...@@ -71,7 +71,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username ...@@ -71,7 +71,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username
export POETRY_HTTP_BASIC_PYPI_PASSWORD=password export POETRY_HTTP_BASIC_PYPI_PASSWORD=password
``` ```
See [Using environment variables](/configuration#using-environment-variables) for more information See [Using environment variables](/docs/configuration/#using-environment-variables) for more information
on how to configure Poetry with environment variables. on how to configure Poetry with environment variables.
#### Custom certificate authority and mutual TLS authentication #### Custom certificate authority and mutual TLS authentication
......
...@@ -14,7 +14,8 @@ nav: ...@@ -14,7 +14,8 @@ nav:
- Commands: cli.md - Commands: cli.md
- Configuration: configuration.md - Configuration: configuration.md
- Repositories: repositories.md - Repositories: repositories.md
- Versions: versions.md - Managing environments: managing-environments.md
- Dependency specification: dependency-specification.md
- The pyproject.toml file: pyproject.md - The pyproject.toml file: pyproject.md
- Contributing: contributing.md - Contributing: contributing.md
- FAQ: faq.md - FAQ: faq.md
...@@ -25,3 +26,5 @@ markdown_extensions: ...@@ -25,3 +26,5 @@ markdown_extensions:
- pymdownx.superfences - pymdownx.superfences
- toc: - toc:
permalink: permalink:
- markdown_include.include:
base_path: docs
""" """
This script will install poetry and its dependencies This script will install Poetry and its dependencies
in isolation from the rest of the system. in isolation from the rest of the system.
It does, in order: It does, in order:
...@@ -65,6 +65,7 @@ try: ...@@ -65,6 +65,7 @@ try:
except NameError: except NameError:
u = str u = str
SHELL = os.getenv("SHELL", "")
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
...@@ -226,8 +227,7 @@ It will add the `poetry` command to {poetry}'s bin directory, located at: ...@@ -226,8 +227,7 @@ It will add the `poetry` command to {poetry}'s bin directory, located at:
{platform_msg} {platform_msg}
You can uninstall at any time with `poetry self:uninstall`, You can uninstall at any time by executing this script with the --uninstall option,
or by executing this script with the --uninstall option,
and these changes will be reverted. and these changes will be reverted.
""" """
...@@ -249,6 +249,9 @@ modifying the profile file{plural} located at: ...@@ -249,6 +249,9 @@ modifying the profile file{plural} located at:
{rcfiles}""" {rcfiles}"""
PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by
modifying the `fish_user_paths` universal variable."""
PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
...@@ -264,6 +267,12 @@ automatically. ...@@ -264,6 +267,12 @@ automatically.
To configure your current shell run `source {poetry_home_env}` To configure your current shell run `source {poetry_home_env}`
""" """
POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great!
{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH`
environment variable by modifying the `fish_user_paths` universal variable.
"""
POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -279,6 +288,15 @@ environment variable. ...@@ -279,6 +288,15 @@ environment variable.
To configure your current shell run `source {poetry_home_env}` To configure your current shell run `source {poetry_home_env}`
""" """
POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin})
in your `PATH` environment variable, which you can add by running
the following command:
set -U fish_user_paths {poetry_home_bin} $fish_user_paths
"""
POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -605,6 +623,9 @@ class Installer: ...@@ -605,6 +623,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
return return
if "fish" in SHELL:
return self.add_to_fish_path()
if WINDOWS: if WINDOWS:
return self.add_to_windows_path() return self.add_to_windows_path()
...@@ -625,6 +646,40 @@ class Installer: ...@@ -625,6 +646,40 @@ class Installer:
with open(profile, "a") as f: with open(profile, "a") as f:
f.write(u(addition)) f.write(u(addition))
def add_to_fish_path(self):
"""
Ensure POETRY_BIN directory is on Fish shell $PATH
"""
current_path = os.environ.get("PATH", None)
if current_path is None:
print(
colorize(
"warning",
"\nUnable to get the PATH value. It will not be updated automatically.",
)
)
self._modify_path = False
return
if POETRY_BIN not in current_path:
fish_user_paths = subprocess.check_output(
["fish", "-c", "echo $fish_user_paths"]
).decode("utf-8")
if POETRY_BIN not in fish_user_paths:
cmd = "set -U fish_user_paths {} $fish_user_paths".format(POETRY_BIN)
set_fish_user_path = ["fish", "-c", "{}".format(cmd)]
subprocess.check_output(set_fish_user_path)
else:
print(
colorize(
"warning",
"\nPATH already contains {} and thus was not modified.".format(
POETRY_BIN
),
)
)
def add_to_windows_path(self): def add_to_windows_path(self):
try: try:
old_path = self.get_windows_path_var() old_path = self.get_windows_path_var()
...@@ -685,11 +740,25 @@ class Installer: ...@@ -685,11 +740,25 @@ class Installer:
) )
def remove_from_path(self): def remove_from_path(self):
if WINDOWS: if "fish" in SHELL:
return self.remove_from_fish_path()
elif WINDOWS:
return self.remove_from_windows_path() return self.remove_from_windows_path()
return self.remove_from_unix_path() return self.remove_from_unix_path()
def remove_from_fish_path(self):
fish_user_paths = subprocess.check_output(
["fish", "-c", "echo $fish_user_paths"]
).decode("utf-8")
if POETRY_BIN in fish_user_paths:
cmd = "set -U fish_user_paths (string match -v {} $fish_user_paths)".format(
POETRY_BIN
)
set_fish_user_path = ["fish", "-c", "{}".format(cmd)]
subprocess.check_output(set_fish_user_path)
def remove_from_windows_path(self): def remove_from_windows_path(self):
path = self.get_windows_path_var() path = self.get_windows_path_var()
...@@ -741,8 +810,7 @@ class Installer: ...@@ -741,8 +810,7 @@ class Installer:
def get_unix_profiles(self): def get_unix_profiles(self):
profiles = [os.path.join(HOME, ".profile")] profiles = [os.path.join(HOME, ".profile")]
shell = os.getenv("SHELL", "") if "zsh" in SHELL:
if "zsh" in shell:
zdotdir = os.getenv("ZDOTDIR", HOME) zdotdir = os.getenv("ZDOTDIR", HOME)
profiles.append(os.path.join(zdotdir, ".zprofile")) profiles.append(os.path.join(zdotdir, ".zprofile"))
...@@ -766,7 +834,9 @@ class Installer: ...@@ -766,7 +834,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH
else: else:
if WINDOWS: if "fish" in SHELL:
kwargs["platform_msg"] = PRE_MESSAGE_FISH
elif WINDOWS:
kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS
else: else:
profiles = [ profiles = [
...@@ -809,6 +879,12 @@ class Installer: ...@@ -809,6 +879,12 @@ class Installer:
poetry_home_bin = POETRY_BIN.replace( poetry_home_bin = POETRY_BIN.replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%" os.getenv("USERPROFILE", ""), "%USERPROFILE%"
) )
elif "fish" in SHELL:
message = POST_MESSAGE_FISH
if not self._modify_path:
message = POST_MESSAGE_FISH_NO_MODIFY_PATH
poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
else: else:
message = POST_MESSAGE_UNIX message = POST_MESSAGE_UNIX
if not self._modify_path: if not self._modify_path:
......
...@@ -3,8 +3,8 @@ PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp3 ...@@ -3,8 +3,8 @@ PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp3
cd /io cd /io
/opt/python/cp37-cp37m/bin/pip install pip -U /opt/python/cp37-cp37m/bin/pip install pip -U
/opt/python/cp37-cp37m/bin/pip install poetry -U /opt/python/cp37-cp37m/bin/pip install poetry -U --pre
/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false /opt/python/cp37-cp37m/bin/poetry config virtualenvs.create false
/opt/python/cp37-cp37m/bin/poetry install --no-dev /opt/python/cp37-cp37m/bin/poetry install --no-dev
/opt/python/cp37-cp37m/bin/python sonnet make release --ansi \ /opt/python/cp37-cp37m/bin/python sonnet make release --ansi \
-P "2.7:/opt/python/cp27-cp27m/bin/python" \ -P "2.7:/opt/python/cp27-cp27m/bin/python" \
......
__version__ = "1.0.0b3" __version__ = "1.0.2"
...@@ -12,5 +12,5 @@ class AboutCommand(Command): ...@@ -12,5 +12,5 @@ class AboutCommand(Command):
"""<info>Poetry - Package Management for Python</info> """<info>Poetry - Package Management for Python</info>
<comment>Poetry is a dependency manager tracking local dependencies of your projects and libraries. <comment>Poetry is a dependency manager tracking local dependencies of your projects and libraries.
See <fg=blue>https://github.com/sdispater/poetry</> for more information.</comment>""" See <fg=blue>https://github.com/python-poetry/poetry</> for more information.</comment>"""
) )
...@@ -5,8 +5,6 @@ from cleo import argument ...@@ -5,8 +5,6 @@ from cleo import argument
from cleo import option from cleo import option
from poetry.factory import Factory from poetry.factory import Factory
from poetry.utils.helpers import keyring_repository_password_del
from poetry.utils.helpers import keyring_repository_password_set
from .command import Command from .command import Command
...@@ -181,11 +179,14 @@ To remove a repository (repo is a short alias for repositories): ...@@ -181,11 +179,14 @@ To remove a repository (repo is a short alias for repositories):
# handle auth # handle auth
m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key"))
if m: if m:
from poetry.utils.password_manager import PasswordManager
password_manager = PasswordManager(config)
if self.option("unset"): if self.option("unset"):
keyring_repository_password_del(config, m.group(2)) if m.group(1) == "http-basic":
config.auth_config_source.remove_property( password_manager.delete_http_password(m.group(2))
"{}.{}".format(m.group(1), m.group(2)) elif m.group(1) == "pypi-token":
) password_manager.delete_pypi_token(m.group(2))
return 0 return 0
...@@ -203,15 +204,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -203,15 +204,7 @@ To remove a repository (repo is a short alias for repositories):
username = values[0] username = values[0]
password = values[1] password = values[1]
property_value = dict(username=username) password_manager.set_http_password(m.group(2), username, password)
try:
keyring_repository_password_set(m.group(2), username, password)
except RuntimeError:
property_value.update(password=password)
config.auth_config_source.add_property(
"{}.{}".format(m.group(1), m.group(2)), property_value
)
elif m.group(1) == "pypi-token": elif m.group(1) == "pypi-token":
if len(values) != 1: if len(values) != 1:
raise ValueError( raise ValueError(
...@@ -220,9 +213,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -220,9 +213,7 @@ To remove a repository (repo is a short alias for repositories):
token = values[0] token = values[0]
config.auth_config_source.add_property( password_manager.set_pypi_token(m.group(2), token)
"{}.{}".format(m.group(1), m.group(2)), token
)
return 0 return 0
......
...@@ -28,10 +28,11 @@ class DebugResolveCommand(InitCommand): ...@@ -28,10 +28,11 @@ class DebugResolveCommand(InitCommand):
loggers = ["poetry.repositories.pypi_repository"] loggers = ["poetry.repositories.pypi_repository"]
def handle(self): def handle(self):
from poetry.io.null_io import NullIO
from poetry.packages import ProjectPackage from poetry.packages import ProjectPackage
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.semver import parse_constraint
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
packages = self.argument("package") packages = self.argument("package")
...@@ -99,20 +100,30 @@ class DebugResolveCommand(InitCommand): ...@@ -99,20 +100,30 @@ class DebugResolveCommand(InitCommand):
return 0 return 0
env = EnvManager(self.poetry).get()
current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info)
)
table = self.table([], style="borderless") table = self.table([], style="borderless")
rows = [] rows = []
if self.option("install"):
env = EnvManager(self.poetry).get()
current_python_version = ".".join(str(v) for v in env.version_info)
pool = Pool()
locked_repository = Repository()
for op in ops:
locked_repository.add_package(op.package)
pool.add_repository(locked_repository)
with package.with_python_versions(current_python_version):
solver = Solver(package, pool, Repository(), Repository(), NullIO())
ops = solver.solve()
for op in ops: for op in ops:
pkg = op.package pkg = op.package
if self.option("install"): row = [
if not pkg.python_constraint.allows( "<c1>{}</c1>".format(pkg.name),
current_python_version "<b>{}</b>".format(pkg.version),
) or not env.is_valid_for_marker(pkg.marker): "",
continue ]
row = ["<c1>{}</c1>".format(pkg.name), "<b>{}</b>".format(pkg.version), ""]
if not pkg.marker.is_any(): if not pkg.marker.is_any():
row[2] = str(pkg.marker) row[2] = str(pkg.marker)
......
...@@ -147,8 +147,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -147,8 +147,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
"You can specify a package in the following forms:\n" "You can specify a package in the following forms:\n"
" - A single name (<b>requests</b>)\n" " - A single name (<b>requests</b>)\n"
" - A name and a constraint (<b>requests ^2.23.0</b>)\n" " - A name and a constraint (<b>requests ^2.23.0</b>)\n"
" - A git url (<b>https://github.com/sdispater/poetry.git</b>)\n" " - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)\n"
" - A git url with a revision (<b>https://github.com/sdispater/poetry.git@develop</b>)\n" " - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n"
" - A file path (<b>../my-package/my-package.whl</b>)\n" " - A file path (<b>../my-package/my-package.whl</b>)\n"
" - A directory (<b>../my-package/</b>)\n" " - A directory (<b>../my-package/</b>)\n"
" - An url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n" " - An url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n"
...@@ -365,22 +365,21 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -365,22 +365,21 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
if url_parsed.scheme and url_parsed.netloc: if url_parsed.scheme and url_parsed.netloc:
# Url # Url
if url_parsed.scheme in ["git+https", "git+ssh"]: if url_parsed.scheme in ["git+https", "git+ssh"]:
url = requirement.lstrip("git+") from poetry.vcs.git import Git
rev = None from poetry.vcs.git import ParsedUrl
if "@" in url:
url, rev = url.split("@")
pair = OrderedDict( parsed = ParsedUrl.parse(requirement)
[("name", url.split("/")[-1].rstrip(".git")), ("git", url)] url = Git.normalize_url(requirement)
)
if rev: pair = OrderedDict([("name", parsed.name), ("git", url.url)])
pair["rev"] = rev if parsed.rev:
pair["rev"] = url.revision
if extras: if extras:
pair["extras"] = extras pair["extras"] = extras
package = Provider.get_package_from_vcs( package = Provider.get_package_from_vcs(
"git", url, reference=pair.get("rev") "git", url.url, reference=pair.get("rev")
) )
pair["name"] = package.name pair["name"] = package.name
result.append(pair) result.append(pair)
...@@ -426,6 +425,11 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -426,6 +425,11 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
require = OrderedDict() require = OrderedDict()
if " " in pair: if " " in pair:
name, version = pair.split(" ", 2) name, version = pair.split(" ", 2)
extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
if extras_m:
extras = [e.strip() for e in extras_m.group(1).split(",")]
name, _ = name.split("[")
require["name"] = name require["name"] = name
if version != "latest": if version != "latest":
require["version"] = version require["version"] = version
......
...@@ -6,8 +6,10 @@ class LockCommand(EnvCommand): ...@@ -6,8 +6,10 @@ class LockCommand(EnvCommand):
name = "lock" name = "lock"
description = "Locks the project dependencies." description = "Locks the project dependencies."
help = """The <info>lock</info> command reads the <comment>pyproject.toml</> file from help = """
the current directory, processes it, and locks the depdencies in the <comment>poetry.lock</> file. The <info>lock</info> command reads the <comment>pyproject.toml</> file from the
current directory, processes it, and locks the dependencies in the <comment>poetry.lock</>
file.
<info>poetry lock</info> <info>poetry lock</info>
""" """
......
...@@ -3,6 +3,8 @@ import sys ...@@ -3,6 +3,8 @@ import sys
from cleo import argument from cleo import argument
from cleo import option from cleo import option
from poetry.utils.helpers import module_name
from .command import Command from .command import Command
...@@ -76,6 +78,6 @@ class NewCommand(Command): ...@@ -76,6 +78,6 @@ class NewCommand(Command):
self.line( self.line(
"Created package <info>{}</> in <fg=blue>{}</>".format( "Created package <info>{}</> in <fg=blue>{}</>".format(
name, path.relative_to(Path.cwd()) module_name(name), path.relative_to(Path.cwd())
) )
) )
...@@ -172,6 +172,13 @@ lists all packages available.""" ...@@ -172,6 +172,13 @@ lists all packages available."""
# Non installed in non decorated mode # Non installed in non decorated mode
install_marker = " (!)" install_marker = " (!)"
if (
show_latest
and self.option("outdated")
and latest_statuses[locked.pretty_name] == "up-to-date"
):
continue
line = "<fg={}>{:{}}{}</>".format( line = "<fg={}>{:{}}{}</>".format(
color, name, name_length - len(install_marker), install_marker color, name, name_length - len(install_marker), install_marker
) )
...@@ -183,9 +190,6 @@ lists all packages available.""" ...@@ -183,9 +190,6 @@ lists all packages available."""
latest = latest_packages[locked.pretty_name] latest = latest_packages[locked.pretty_name]
update_status = latest_statuses[locked.pretty_name] update_status = latest_statuses[locked.pretty_name]
if self.option("outdated") and update_status == "up-to-date":
continue
if write_latest: if write_latest:
color = "green" color = "green"
if update_status == "semver-safe-update": if update_status == "semver-safe-update":
......
...@@ -7,7 +7,7 @@ class VersionCommand(Command): ...@@ -7,7 +7,7 @@ class VersionCommand(Command):
name = "version" name = "version"
description = ( description = (
"Shows the version of the project or bumps it when a valid" "Shows the version of the project or bumps it when a valid "
"bump rule is provided." "bump rule is provided."
) )
...@@ -59,7 +59,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -59,7 +59,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
self.poetry.file.write(content) self.poetry.file.write(content)
else: else:
self.line( self.line(
"Project (<comment>{}</>) version is <info>{}</>".format( "<comment>{}</> <info>{}</>".format(
self.poetry.package.name, self.poetry.package.pretty_version self.poetry.package.name, self.poetry.package.pretty_version
) )
) )
......
import logging import logging
from typing import Any
from cleo.config import ApplicationConfig as BaseApplicationConfig from cleo.config import ApplicationConfig as BaseApplicationConfig
from clikit.api.application.application import Application from clikit.api.application.application import Application
from clikit.api.args.raw_args import RawArgs from clikit.api.args.raw_args import RawArgs
...@@ -43,15 +45,15 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -43,15 +45,15 @@ class ApplicationConfig(BaseApplicationConfig):
self.add_event_listener(PRE_HANDLE, self.set_env) self.add_event_listener(PRE_HANDLE, self.set_env)
def register_command_loggers( def register_command_loggers(
self, event, event_name, _ # type: PreHandleEvent # type: str self, event, event_name, _
): # type: (...) -> None ): # type: (PreHandleEvent, str, Any) -> None
command = event.command.config.handler command = event.command.config.handler
if not isinstance(command, Command): if not isinstance(command, Command):
return return
io = event.io io = event.io
loggers = ["poetry.packages.package"] loggers = ["poetry.packages.package", "poetry.utils.password_manager"]
loggers += command.loggers loggers += command.loggers
...@@ -72,7 +74,7 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -72,7 +74,7 @@ class ApplicationConfig(BaseApplicationConfig):
logger.setLevel(level) logger.setLevel(level)
def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, _) -> None def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, Any) -> None
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
command = event.command.config.handler # type: EnvCommand command = event.command.config.handler # type: EnvCommand
......
...@@ -233,7 +233,8 @@ class Factory: ...@@ -233,7 +233,8 @@ class Factory:
): # type: (Dict[str, str], Config) -> LegacyRepository ): # type: (Dict[str, str], Config) -> LegacyRepository
from .repositories.auth import Auth from .repositories.auth import Auth
from .repositories.legacy_repository import LegacyRepository from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_client_cert, get_cert, get_http_basic_auth from .utils.helpers import get_client_cert, get_cert
from .utils.password_manager import PasswordManager
if "url" in source: if "url" in source:
# PyPI-like repository # PyPI-like repository
...@@ -242,11 +243,12 @@ class Factory: ...@@ -242,11 +243,12 @@ class Factory:
else: else:
raise RuntimeError("Unsupported source specified") raise RuntimeError("Unsupported source specified")
password_manager = PasswordManager(auth_config)
name = source["name"] name = source["name"]
url = source["url"] url = source["url"]
credentials = get_http_basic_auth(auth_config, name) credentials = password_manager.get_http_auth(name)
if credentials: if credentials:
auth = Auth(url, credentials[0], credentials[1]) auth = Auth(url, credentials["username"], credentials["password"])
else: else:
auth = None auth = None
...@@ -298,7 +300,7 @@ class Factory: ...@@ -298,7 +300,7 @@ class Factory:
result["warnings"].append( result["warnings"].append(
'The "{}" dependency specifies ' 'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. ' 'the "allows-prereleases" property, which is deprecated. '
'Use "allow-preleases" instead.'.format(name) 'Use "allow-prereleases" instead.'.format(name)
) )
# Checking for scripts with extras # Checking for scripts with extras
......
...@@ -234,7 +234,7 @@ class Installer: ...@@ -234,7 +234,7 @@ class Installer:
# Execute operations # Execute operations
actual_ops = [op for op in ops if not op.skipped] actual_ops = [op for op in ops if not op.skipped]
if not actual_ops and (self._execute_operations or self._dry_run): if not actual_ops and (self._execute_operations or self._dry_run):
self._io.write_line("Nothing to install or update") self._io.write_line("No dependencies to install or update")
if actual_ops and (self._execute_operations or self._dry_run): if actual_ops and (self._execute_operations or self._dry_run):
installs = [] installs = []
......
...@@ -40,7 +40,10 @@ class PipInstaller(BaseInstaller): ...@@ -40,7 +40,10 @@ class PipInstaller(BaseInstaller):
args = ["install", "--no-deps"] args = ["install", "--no-deps"]
if package.source_type == "legacy" and package.source_url: if (
package.source_type not in {"git", "directory", "file", "url"}
and package.source_url
):
repository = self._pool.repository(package.source_reference) repository = self._pool.repository(package.source_reference)
parsed = urlparse.urlparse(package.source_url) parsed = urlparse.urlparse(package.source_url)
if parsed.scheme == "http": if parsed.scheme == "http":
...@@ -70,7 +73,7 @@ class PipInstaller(BaseInstaller): ...@@ -70,7 +73,7 @@ class PipInstaller(BaseInstaller):
if update: if update:
args.append("-U") args.append("-U")
if package.files and not package.source_type: if package.files and not package.source_url:
# Format as a requirements.txt # Format as a requirements.txt
# We need to create a requirements.txt file # We need to create a requirements.txt file
# for each package in order to check hashes. # for each package in order to check hashes.
...@@ -93,7 +96,12 @@ class PipInstaller(BaseInstaller): ...@@ -93,7 +96,12 @@ class PipInstaller(BaseInstaller):
self.run(*args) self.run(*args)
def update(self, _, target): def update(self, package, target):
if package.source_type != target.source_type:
# If the source type has changed, we remove the current
# package to avoid perpetual updates in some cases
self.remove(package)
self.install(target, update=True) self.install(target, update=True)
def remove(self, package): def remove(self, package):
...@@ -112,7 +120,7 @@ class PipInstaller(BaseInstaller): ...@@ -112,7 +120,7 @@ class PipInstaller(BaseInstaller):
raise raise
def run(self, *args, **kwargs): # type: (...) -> str def run(self, *args, **kwargs): # type: (...) -> str
return self._env.run("python", "-m", "pip", *args, **kwargs) return self._env.run_pip(*args, **kwargs)
def requirement(self, package, formatted=False): def requirement(self, package, formatted=False):
if formatted and not package.source_type: if formatted and not package.source_type:
......
...@@ -11,7 +11,6 @@ from typing import Union ...@@ -11,7 +11,6 @@ from typing import Union
from clikit.api.io.flags import VERY_VERBOSE from clikit.api.io.flags import VERY_VERBOSE
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import glob from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache from poetry.utils._compat import lru_cache
from poetry.utils._compat import to_str from poetry.utils._compat import to_str
...@@ -84,15 +83,6 @@ class Builder(object): ...@@ -84,15 +83,6 @@ class Builder(object):
explicitely_excluded = set() explicitely_excluded = set()
for excluded_glob in self._package.exclude: for excluded_glob in self._package.exclude:
excluded_path = Path(self._path, excluded_glob)
try:
is_dir = excluded_path.is_dir()
except OSError:
# On Windows, testing if a path with a glob is a directory will raise an OSError
is_dir = False
if is_dir:
excluded_glob = Path(excluded_glob, "**/*")
for excluded in glob( for excluded in glob(
Path(self._path, excluded_glob).as_posix(), recursive=True Path(self._path, excluded_glob).as_posix(), recursive=True
...@@ -112,10 +102,18 @@ class Builder(object): ...@@ -112,10 +102,18 @@ class Builder(object):
return result return result
def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool
if not isinstance(filepath, basestring): exclude_path = Path(filepath)
filepath = filepath.as_posix()
while True:
if exclude_path.as_posix() in self.find_excluded_files():
return True
if len(exclude_path.parts) > 1:
exclude_path = exclude_path.parent
else:
break
return filepath in self.find_excluded_files() return False
def find_files_to_add(self, exclude_build=True): # type: (bool) -> list def find_files_to_add(self, exclude_build=True): # type: (bool) -> list
""" """
...@@ -164,7 +162,7 @@ class Builder(object): ...@@ -164,7 +162,7 @@ class Builder(object):
) )
to_add.append(license_file.relative_to(self._path)) to_add.append(license_file.relative_to(self._path))
# If a README is specificed we need to include it # If a README is specified we need to include it
# to avoid errors # to avoid errors
if "readme" in self._poetry.local_config: if "readme" in self._poetry.local_config:
readme = self._path / self._poetry.local_config["readme"] readme = self._path / self._poetry.local_config["readme"]
......
...@@ -31,16 +31,14 @@ class EditableBuilder(Builder): ...@@ -31,16 +31,14 @@ class EditableBuilder(Builder):
try: try:
if self._env.pip_version < Version(19, 0): if self._env.pip_version < Version(19, 0):
self._env.run("python", "-m", "pip", "install", "-e", str(self._path)) self._env.run_pip("install", "-e", str(self._path))
else: else:
# Temporarily rename pyproject.toml # Temporarily rename pyproject.toml
shutil.move( shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
) )
try: try:
self._env.run( self._env.run_pip("install", "-e", str(self._path))
"python", "-m", "pip", "install", "-e", str(self._path)
)
finally: finally:
shutil.move( shutil.move(
str(self._poetry.file.with_suffix(".tmp")), str(self._poetry.file.with_suffix(".tmp")),
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import os import os
import re import re
import tarfile import tarfile
import time
from collections import defaultdict from collections import defaultdict
from copy import copy from copy import copy
...@@ -83,12 +84,14 @@ class SdistBuilder(Builder): ...@@ -83,12 +84,14 @@ class SdistBuilder(Builder):
setup = self.build_setup() setup = self.build_setup()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py"))
tar_info.size = len(setup) tar_info.size = len(setup)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(setup)) tar.addfile(tar_info, BytesIO(setup))
pkg_info = self.build_pkg_info() pkg_info = self.build_pkg_info()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO")) tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO"))
tar_info.size = len(pkg_info) tar_info.size = len(pkg_info)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(pkg_info)) tar.addfile(tar_info, BytesIO(pkg_info))
finally: finally:
tar.close() tar.close()
...@@ -234,7 +237,7 @@ class SdistBuilder(Builder): ...@@ -234,7 +237,7 @@ class SdistBuilder(Builder):
if from_top_level == ".": if from_top_level == ".":
continue continue
is_subpkg = "__init__.py" in filenames is_subpkg = any([filename.endswith(".py") for filename in filenames])
if is_subpkg: if is_subpkg:
subpkg_paths.add(from_top_level) subpkg_paths.add(from_top_level)
parts = from_top_level.split(os.sep) parts = from_top_level.split(os.sep)
......
...@@ -115,9 +115,9 @@ class WheelBuilder(Builder): ...@@ -115,9 +115,9 @@ class WheelBuilder(Builder):
return return
lib = lib[0] lib = lib[0]
excluded = self.find_excluded_files()
for pkg in lib.glob("**/*"): for pkg in lib.glob("**/*"):
if pkg.is_dir() or pkg in excluded: if pkg.is_dir() or self.is_excluded(pkg):
continue continue
rel_path = str(pkg.relative_to(lib)) rel_path = str(pkg.relative_to(lib))
...@@ -132,7 +132,7 @@ class WheelBuilder(Builder): ...@@ -132,7 +132,7 @@ class WheelBuilder(Builder):
self._add_file(wheel, pkg, rel_path) self._add_file(wheel, pkg, rel_path)
def _copy_module(self, wheel): def _copy_module(self, wheel):
excluded = self.find_excluded_files()
to_add = [] to_add = []
for include in self._module.includes: for include in self._module.includes:
...@@ -153,7 +153,9 @@ class WheelBuilder(Builder): ...@@ -153,7 +153,9 @@ class WheelBuilder(Builder):
else: else:
rel_file = file.relative_to(self._path) rel_file = file.relative_to(self._path)
if rel_file.as_posix() in excluded: if self.is_excluded(rel_file.as_posix()) and isinstance(
include, PackageInclude
):
continue continue
if file.suffix == ".pyc": if file.suffix == ".pyc":
......
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_cert
from poetry.utils.helpers import get_client_cert from poetry.utils.helpers import get_client_cert
from poetry.utils.helpers import get_http_basic_auth from poetry.utils.password_manager import PasswordManager
from .uploader import Uploader from .uploader import Uploader
...@@ -20,6 +20,7 @@ class Publisher: ...@@ -20,6 +20,7 @@ class Publisher:
self._package = poetry.package self._package = poetry.package
self._io = io self._io = io
self._uploader = Uploader(poetry, io) self._uploader = Uploader(poetry, io)
self._password_manager = PasswordManager(poetry.config)
@property @property
def files(self): def files(self):
...@@ -60,21 +61,21 @@ class Publisher: ...@@ -60,21 +61,21 @@ class Publisher:
if not (username and password): if not (username and password):
# Check if we have a token first # Check if we have a token first
token = self._poetry.config.get("pypi-token.{}".format(repository_name)) token = self._password_manager.get_pypi_token(repository_name)
if token: if token:
logger.debug("Found an API token for {}.".format(repository_name)) logger.debug("Found an API token for {}.".format(repository_name))
username = "__token__" username = "__token__"
password = token password = token
else: else:
auth = get_http_basic_auth(self._poetry.config, repository_name) auth = self._password_manager.get_http_auth(repository_name)
if auth: if auth:
logger.debug( logger.debug(
"Found authentication information for {}.".format( "Found authentication information for {}.".format(
repository_name repository_name
) )
) )
username = auth[0] username = auth["username"]
password = auth[1] password = auth["password"]
resolved_client_cert = client_cert or get_client_cert( resolved_client_cert = client_cert or get_client_cert(
self._poetry.config, repository_name self._poetry.config, repository_name
......
...@@ -12,7 +12,6 @@ class PackageInclude(Include): ...@@ -12,7 +12,6 @@ class PackageInclude(Include):
base = base / source base = base / source
super(PackageInclude, self).__init__(base, include, formats=formats) super(PackageInclude, self).__init__(base, include, formats=formats)
self.check_elements() self.check_elements()
@property @property
...@@ -35,6 +34,8 @@ class PackageInclude(Include): ...@@ -35,6 +34,8 @@ class PackageInclude(Include):
return self.check_elements() return self.check_elements()
def check_elements(self): # type: () -> PackageInclude def check_elements(self): # type: () -> PackageInclude
root = self._elements[0]
if not self._elements: if not self._elements:
raise ValueError( raise ValueError(
"{} does not contain any element".format(self._base / self._include) "{} does not contain any element".format(self._base / self._include)
...@@ -44,20 +45,24 @@ class PackageInclude(Include): ...@@ -44,20 +45,24 @@ class PackageInclude(Include):
# Probably glob # Probably glob
self._is_package = True self._is_package = True
# The __init__.py file should be first # Packages no longer need an __init__.py in python3, but there must
root = self._elements[0] # at least be one .py file for it to be considered a package
if root.name != "__init__.py": if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root)) raise ValueError("{} is not a package.".format(root.name))
self._package = root.parent.name self._package = root.parent.name
else: else:
if self._elements[0].is_dir(): if root.is_dir():
# If it's a directory, we include everything inside it # If it's a directory, we include everything inside it
self._package = self._elements[0].name self._package = root.name
self._elements = sorted(list(self._elements[0].glob("**/*"))) self._elements = sorted(list(root.glob("**/*")))
if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root.name))
self._is_package = True self._is_package = True
else: else:
self._package = self._elements[0].stem self._package = root.stem
self._is_module = True self._is_module = True
return self return self
...@@ -31,18 +31,27 @@ class _Writer: ...@@ -31,18 +31,27 @@ class _Writer:
def write(self): def write(self):
buffer = [] buffer = []
required_python_version = None required_python_version_notification = False
for incompatibility in self._root.external_incompatibilities: for incompatibility in self._root.external_incompatibilities:
if isinstance(incompatibility.cause, PythonCause): if isinstance(incompatibility.cause, PythonCause):
required_python_version = incompatibility.cause.root_python_version if not required_python_version_notification:
break buffer.append(
"The current project's Python requirement ({}) "
"is not compatible with some of the required "
"packages Python requirement:".format(
incompatibility.cause.root_python_version
)
)
required_python_version_notification = True
if required_python_version is not None:
buffer.append( buffer.append(
"The current project must support the following Python versions: {}".format( " - {} requires Python {}".format(
required_python_version incompatibility.terms[0].dependency.name,
incompatibility.cause.python_version,
) )
) )
if required_python_version_notification:
buffer.append("") buffer.append("")
if isinstance(self._root.cause, ConflictCause): if isinstance(self._root.cause, ConflictCause):
......
...@@ -25,6 +25,8 @@ from .vcs_dependency import VCSDependency ...@@ -25,6 +25,8 @@ from .vcs_dependency import VCSDependency
def dependency_from_pep_508(name): def dependency_from_pep_508(name):
from poetry.vcs.git import ParsedUrl
# Removing comments # Removing comments
parts = name.split("#", 1) parts = name.split("#", 1)
name = parts[0].strip() name = parts[0].strip()
...@@ -46,6 +48,8 @@ def dependency_from_pep_508(name): ...@@ -46,6 +48,8 @@ def dependency_from_pep_508(name):
if is_url(name): if is_url(name):
link = Link(name) link = Link(name)
elif req.url:
link = Link(req.url)
else: else:
p, extras = strip_extras(path) p, extras = strip_extras(path)
if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")): if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")):
...@@ -74,10 +78,15 @@ def dependency_from_pep_508(name): ...@@ -74,10 +78,15 @@ def dependency_from_pep_508(name):
version = m.group("ver") version = m.group("ver")
dep = Dependency(name, version) dep = Dependency(name, version)
else: else:
name = link.egg_fragment name = req.name or link.egg_fragment
if link.scheme == "git": if link.scheme.startswith("git+"):
url = ParsedUrl.parse(link.url)
dep = VCSDependency(name, "git", url.url, rev=url.rev)
elif link.scheme == "git":
dep = VCSDependency(name, "git", link.url_without_fragment) dep = VCSDependency(name, "git", link.url_without_fragment)
elif link.scheme in ["http", "https"]:
dep = URLDependency(name, link.url_without_fragment)
else: else:
dep = Dependency(name, "*") dep = Dependency(name, "*")
else: else:
......
...@@ -55,6 +55,7 @@ class Dependency(object): ...@@ -55,6 +55,7 @@ class Dependency(object):
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._transitive_python_versions = None self._transitive_python_versions = None
self._transitive_python_constraint = None self._transitive_python_constraint = None
self._transitive_marker = None
self._extras = [] self._extras = []
self._in_extras = [] self._in_extras = []
...@@ -118,6 +119,17 @@ class Dependency(object): ...@@ -118,6 +119,17 @@ class Dependency(object):
self._transitive_python_constraint = parse_constraint(value) self._transitive_python_constraint = parse_constraint(value)
@property @property
def transitive_marker(self):
if self._transitive_marker is None:
return self.marker
return self._transitive_marker
@transitive_marker.setter
def transitive_marker(self, value):
self._transitive_marker = value
@property
def python_constraint(self): def python_constraint(self):
return self._python_constraint return self._python_constraint
......
...@@ -8,6 +8,7 @@ from tomlkit import document ...@@ -8,6 +8,7 @@ from tomlkit import document
from tomlkit import inline_table from tomlkit import inline_table
from tomlkit import item from tomlkit import item
from tomlkit import table from tomlkit import table
from tomlkit.exceptions import TOMLKitError
import poetry.packages import poetry.packages
import poetry.repositories import poetry.repositories
...@@ -137,8 +138,11 @@ class Locker(object): ...@@ -137,8 +138,11 @@ class Locker(object):
package.add_dependency(dep_name, constraint) package.add_dependency(dep_name, constraint)
if "develop" in info:
package.develop = info["develop"]
if "source" in info: if "source" in info:
package.source_type = info["source"]["type"] package.source_type = info["source"].get("type", "")
package.source_url = info["source"]["url"] package.source_url = info["source"]["url"]
package.source_reference = info["source"]["reference"] package.source_reference = info["source"]["reference"]
...@@ -217,7 +221,10 @@ class Locker(object): ...@@ -217,7 +221,10 @@ class Locker(object):
if not self._lock.exists(): if not self._lock.exists():
raise RuntimeError("No lockfile found. Unable to read locked packages") raise RuntimeError("No lockfile found. Unable to read locked packages")
try:
return self._lock.read() return self._lock.read()
except TOMLKitError as e:
raise RuntimeError("Unable to read the lock file ({}).".format(e))
def _lock_packages( def _lock_packages(
self, packages self, packages
...@@ -290,11 +297,14 @@ class Locker(object): ...@@ -290,11 +297,14 @@ class Locker(object):
data["dependencies"] = dependencies data["dependencies"] = dependencies
if package.source_type: if package.source_url:
data["source"] = { data["source"] = {
"type": package.source_type,
"url": package.source_url, "url": package.source_url,
"reference": package.source_reference, "reference": package.source_reference,
} }
if package.source_type:
data["source"]["type"] = package.source_type
if package.source_type == "directory":
data["develop"] = package.develop
return data return data
...@@ -279,7 +279,7 @@ class Package(object): ...@@ -279,7 +279,7 @@ class Package(object):
message = ( message = (
'The "{}" dependency specifies ' 'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. ' 'the "allows-prereleases" property, which is deprecated. '
'Use "allow-preleases" instead.'.format(name) 'Use "allow-prereleases" instead.'.format(name)
) )
warn(message, DeprecationWarning) warn(message, DeprecationWarning)
logger.warning(message) logger.warning(message)
...@@ -297,6 +297,7 @@ class Package(object): ...@@ -297,6 +297,7 @@ class Package(object):
branch=constraint.get("branch", None), branch=constraint.get("branch", None),
tag=constraint.get("tag", None), tag=constraint.get("tag", None),
rev=constraint.get("rev", None), rev=constraint.get("rev", None),
category=category,
optional=optional, optional=optional,
) )
elif "file" in constraint: elif "file" in constraint:
......
...@@ -5,8 +5,13 @@ import re ...@@ -5,8 +5,13 @@ import re
from poetry.packages.constraints.constraint import Constraint from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.multi_constraint import MultiConstraint from poetry.packages.constraints.multi_constraint import MultiConstraint
from poetry.packages.constraints.union_constraint import UnionConstraint from poetry.packages.constraints.union_constraint import UnionConstraint
from poetry.semver import EmptyConstraint
from poetry.semver import Version from poetry.semver import Version
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import VersionUnion from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
from poetry.version.markers import BaseMarker
from poetry.version.markers import MarkerUnion from poetry.version.markers import MarkerUnion
from poetry.version.markers import MultiMarker from poetry.version.markers import MultiMarker
from poetry.version.markers import SingleMarker from poetry.version.markers import SingleMarker
...@@ -236,3 +241,66 @@ def create_nested_marker(name, constraint): ...@@ -236,3 +241,66 @@ def create_nested_marker(name, constraint):
marker = '{} {} "{}"'.format(name, op, version) marker = '{} {} "{}"'.format(name, op, version)
return marker return marker
def get_python_constraint_from_marker(
marker,
): # type: (BaseMarker) -> VersionConstraint
python_marker = marker.only("python_version")
if python_marker.is_any():
return VersionRange()
if python_marker.is_empty():
return EmptyConstraint()
markers = convert_markers(marker)
ors = []
for or_ in markers["python_version"]:
ands = []
for op, version in or_:
# Expand python version
if op == "==":
version = "~" + version
op = ""
elif op == "!=":
version += ".*"
elif op in ("<=", ">"):
parsed_version = Version.parse(version)
if parsed_version.precision == 1:
if op == "<=":
op = "<"
version = parsed_version.next_major.text
elif op == ">":
op = ">="
version = parsed_version.next_major.text
elif parsed_version.precision == 2:
if op == "<=":
op = "<"
version = parsed_version.next_minor.text
elif op == ">":
op = ">="
version = parsed_version.next_minor.text
elif op in ("in", "not in"):
versions = []
for v in re.split("[ ,]+", version):
split = v.split(".")
if len(split) in [1, 2]:
split.append("*")
op_ = "" if op == "in" else "!="
else:
op_ = "==" if op == "in" else "!="
versions.append(op_ + ".".join(split))
glue = " || " if op == "in" else ", "
if versions:
ands.append(glue.join(versions))
continue
ands.append("{}{}".format(op, version))
ors.append(" ".join(ands))
return parse_constraint(" || ".join(ors))
from poetry.vcs import git
from .dependency import Dependency from .dependency import Dependency
...@@ -7,7 +9,15 @@ class VCSDependency(Dependency): ...@@ -7,7 +9,15 @@ class VCSDependency(Dependency):
""" """
def __init__( def __init__(
self, name, vcs, source, branch=None, tag=None, rev=None, optional=False self,
name,
vcs,
source,
branch=None,
tag=None,
rev=None,
category="main",
optional=False,
): ):
self._vcs = vcs self._vcs = vcs
self._source = source self._source = source
...@@ -21,7 +31,7 @@ class VCSDependency(Dependency): ...@@ -21,7 +31,7 @@ class VCSDependency(Dependency):
self._rev = rev self._rev = rev
super(VCSDependency, self).__init__( super(VCSDependency, self).__init__(
name, "*", optional=optional, allows_prereleases=True name, "*", category=category, optional=optional, allows_prereleases=True
) )
@property @property
...@@ -65,11 +75,17 @@ class VCSDependency(Dependency): ...@@ -65,11 +75,17 @@ class VCSDependency(Dependency):
@property @property
def base_pep_508_name(self): # type: () -> str def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name requirement = self.pretty_name
parsed_url = git.ParsedUrl.parse(self._source)
if self.extras: if self.extras:
requirement += "[{}]".format(",".join(self.extras)) requirement += "[{}]".format(",".join(self.extras))
if parsed_url.protocol is not None:
requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference) requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference)
else:
requirement += " @ {}+ssh://{}@{}".format(
self._vcs, parsed_url.format(), self.reference
)
return requirement return requirement
......
...@@ -6,6 +6,7 @@ import time ...@@ -6,6 +6,7 @@ import time
from contextlib import contextmanager from contextlib import contextmanager
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any
from typing import List from typing import List
from typing import Optional from typing import Optional
...@@ -27,6 +28,7 @@ from poetry.packages import PackageCollection ...@@ -27,6 +28,7 @@ from poetry.packages import PackageCollection
from poetry.packages import URLDependency from poetry.packages import URLDependency
from poetry.packages import VCSDependency from poetry.packages import VCSDependency
from poetry.packages import dependency_from_pep_508 from poetry.packages import dependency_from_pep_508
from poetry.packages.utils.utils import get_python_constraint_from_marker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.utils._compat import PY35 from poetry.utils._compat import PY35
from poetry.utils._compat import OrderedDict from poetry.utils._compat import OrderedDict
...@@ -61,9 +63,7 @@ class Provider: ...@@ -61,9 +63,7 @@ class Provider:
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"} UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}
def __init__( def __init__(self, package, pool, io): # type: (Package, Pool, Any) -> None
self, package, pool, io # type: Package # type: Pool
): # type: (...) -> None
self._package = package self._package = package
self._pool = pool self._pool = pool
self._io = io self._io = io
...@@ -275,6 +275,7 @@ class Provider: ...@@ -275,6 +275,7 @@ class Provider:
) )
package.source_url = dependency.path.as_posix() package.source_url = dependency.path.as_posix()
package.develop = dependency.develop
if dependency.base is not None: if dependency.base is not None:
package.root_dir = dependency.base.as_posix() package.root_dir = dependency.base.as_posix()
...@@ -489,14 +490,15 @@ class Provider: ...@@ -489,14 +490,15 @@ class Provider:
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(
self._package.python_constraint self._package.python_constraint
): ):
intersection = package.python_constraint.intersect( transitive_python_constraint = get_python_constraint_from_marker(
package.dependency.transitive_python_constraint package.dependency.transitive_marker
) )
difference = package.dependency.transitive_python_constraint.difference( intersection = package.python_constraint.intersect(
intersection transitive_python_constraint
) )
difference = transitive_python_constraint.difference(intersection)
if ( if (
package.dependency.transitive_python_constraint.is_any() transitive_python_constraint.is_any()
or self._package.python_constraint.intersect( or self._package.python_constraint.intersect(
package.dependency.python_constraint package.dependency.python_constraint
).is_empty() ).is_empty()
...@@ -604,7 +606,7 @@ class Provider: ...@@ -604,7 +606,7 @@ class Provider:
new_markers = [] new_markers = []
for dep in _deps: for dep in _deps:
marker = dep.marker.without_extras() marker = dep.marker.without_extras()
if marker.is_empty(): if marker.is_any():
# No marker or only extras # No marker or only extras
continue continue
...@@ -671,13 +673,28 @@ class Provider: ...@@ -671,13 +673,28 @@ class Provider:
raise CompatibilityError(*python_constraints) raise CompatibilityError(*python_constraints)
# Modifying dependencies as needed # Modifying dependencies as needed
clean_dependencies = []
for dep in dependencies: for dep in dependencies:
if not package.dependency.transitive_marker.without_extras().is_any():
marker_intersection = package.dependency.transitive_marker.without_extras().intersect(
dep.marker.without_extras()
)
if marker_intersection.is_empty():
# The dependency is not needed, since the markers specified
# for the current package selection are not compatible with
# the markers for the current dependency, so we skip it
continue
dep.transitive_marker = marker_intersection
if not package.dependency.python_constraint.is_any(): if not package.dependency.python_constraint.is_any():
dep.transitive_python_versions = str( python_constraint_intersection = dep.python_constraint.intersect(
dep.python_constraint.intersect(
package.dependency.python_constraint package.dependency.python_constraint
) )
) if python_constraint_intersection.is_empty():
# This dependency is not needed under current python constraint.
continue
dep.transitive_python_versions = str(python_constraint_intersection)
if (package.dependency.is_directory() or package.dependency.is_file()) and ( if (package.dependency.is_directory() or package.dependency.is_file()) and (
dep.is_directory() or dep.is_file() dep.is_directory() or dep.is_file()
...@@ -691,8 +708,9 @@ class Provider: ...@@ -691,8 +708,9 @@ class Provider:
# TODO: Improve the way we set the correct relative path for dependencies # TODO: Improve the way we set the correct relative path for dependencies
dep._path = relative dep._path = relative
clean_dependencies.append(dep)
package.requires = dependencies package.requires = clean_dependencies
return package return package
......
...@@ -56,12 +56,20 @@ class Solver: ...@@ -56,12 +56,20 @@ class Solver:
installed = True installed = True
if pkg.source_type == "git" and package.source_type == "git": if pkg.source_type == "git" and package.source_type == "git":
from poetry.vcs.git import Git
# Trying to find the currently installed version # Trying to find the currently installed version
pkg_source_url = Git.normalize_url(pkg.source_url)
package_source_url = Git.normalize_url(package.source_url)
for locked in self._locked.packages: for locked in self._locked.packages:
if locked.name != pkg.name or locked.source_type != "git":
continue
locked_source_url = Git.normalize_url(locked.source_url)
if ( if (
locked.name == pkg.name locked.name == pkg.name
and locked.source_type == pkg.source_type and locked.source_type == pkg.source_type
and locked.source_url == pkg.source_url and locked_source_url == pkg_source_url
and locked.source_reference == pkg.source_reference and locked.source_reference == pkg.source_reference
): ):
pkg = Package(pkg.name, locked.version) pkg = Package(pkg.name, locked.version)
...@@ -70,7 +78,7 @@ class Solver: ...@@ -70,7 +78,7 @@ class Solver:
pkg.source_reference = locked.source_reference pkg.source_reference = locked.source_reference
break break
if pkg.source_url != package.source_url or ( if pkg_source_url != package_source_url or (
pkg.source_reference != package.source_reference pkg.source_reference != package.source_reference
and not pkg.source_reference.startswith( and not pkg.source_reference.startswith(
package.source_reference package.source_reference
...@@ -84,6 +92,8 @@ class Solver: ...@@ -84,6 +92,8 @@ class Solver:
elif package.version != pkg.version: elif package.version != pkg.version:
# Checking version # Checking version
operations.append(Update(pkg, package)) operations.append(Update(pkg, package))
elif package.source_type != pkg.source_type:
operations.append(Update(pkg, package))
else: else:
operations.append(Install(package).skip("Already installed")) operations.append(Install(package).skip("Already installed"))
...@@ -212,8 +222,12 @@ class Solver: ...@@ -212,8 +222,12 @@ class Solver:
else: else:
category = dep.category category = dep.category
optional = dep.is_optional() and not dep.is_activated() optional = dep.is_optional() and not dep.is_activated()
intersection = previous["marker"].intersect(previous_dep.marker) intersection = (
intersection = intersection.intersect(package.marker) previous["marker"]
.without_extras()
.intersect(previous_dep.transitive_marker.without_extras())
)
intersection = intersection.intersect(package.marker.without_extras())
marker = intersection marker = intersection
......
import re
from poetry.packages import Package from poetry.packages import Package
from poetry.utils._compat import Path
from poetry.utils._compat import metadata
from poetry.utils.env import Env from poetry.utils.env import Env
from .repository import Repository from .repository import Repository
...@@ -15,30 +15,52 @@ class InstalledRepository(Repository): ...@@ -15,30 +15,52 @@ class InstalledRepository(Repository):
For now, it uses the pip "freeze" command. For now, it uses the pip "freeze" command.
""" """
repo = cls() repo = cls()
seen = set()
freeze_output = env.run("python", "-m", "pip", "freeze") for entry in env.sys_path:
for line in freeze_output.split("\n"): for distribution in sorted(
if "==" in line: metadata.distributions(path=[entry]), key=lambda d: str(d._path),
name, version = re.split("={2,3}", line) ):
repo.add_package(Package(name, version, version)) name = distribution.metadata["name"]
elif line.startswith("-e "): version = distribution.metadata["version"]
line = line[3:].strip() package = Package(name, version, version)
if line.startswith("git+"): package.description = distribution.metadata.get("summary", "")
url = line.lstrip("git+")
if "@" in url: if package.name in seen:
url, rev = url.rsplit("@", 1) continue
else:
rev = "master" seen.add(package.name)
name = url.split("/")[-1].rstrip(".git")
if "#egg=" in rev:
rev, name = rev.split("#egg=")
package = Package(name, "0.0.0")
package.source_type = "git"
package.source_url = url
package.source_reference = rev
repo.add_package(package) repo.add_package(package)
path = Path(str(distribution._path))
is_standard_package = True
try:
path.relative_to(env.site_packages)
except ValueError:
is_standard_package = False
if is_standard_package:
continue
src_path = env.path / "src"
# A VCS dependency should have been installed
# in the src directory. If not, it's a path dependency
try:
path.relative_to(src_path)
from poetry.vcs.git import Git
git = Git()
revision = git.rev_parse("HEAD", src_path / package.name).strip()
url = git.remote_url(src_path / package.name)
package.source_type = "git"
package.source_url = url
package.source_reference = revision
except ValueError:
package.source_type = "directory"
package.source_url = str(path.parent)
return repo return repo
...@@ -269,7 +269,6 @@ class LegacyRepository(PyPiRepository): ...@@ -269,7 +269,6 @@ class LegacyRepository(PyPiRepository):
for version in versions: for version in versions:
package = Package(name, version) package = Package(name, version)
package.source_type = "legacy"
package.source_url = self._url package.source_url = self._url
if extras is not None: if extras is not None:
...@@ -314,7 +313,6 @@ class LegacyRepository(PyPiRepository): ...@@ -314,7 +313,6 @@ class LegacyRepository(PyPiRepository):
if release_info["requires_python"]: if release_info["requires_python"]:
package.python_versions = release_info["requires_python"] package.python_versions = release_info["requires_python"]
package.source_type = "legacy"
package.source_url = self._url package.source_url = self._url
package.source_reference = self.name package.source_reference = self.name
......
...@@ -8,10 +8,12 @@ from typing import Union ...@@ -8,10 +8,12 @@ from typing import Union
from cachecontrol import CacheControl from cachecontrol import CacheControl
from cachecontrol.caches.file_cache import FileCache from cachecontrol.caches.file_cache import FileCache
from cachecontrol.controller import logger as cache_control_logger
from cachy import CacheManager from cachy import CacheManager
from html5lib.html5parser import parse from html5lib.html5parser import parse
from requests import get from requests import get
from requests import session from requests import session
from requests.exceptions import TooManyRedirects
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.packages import Package from poetry.packages import Package
...@@ -39,12 +41,14 @@ except ImportError: ...@@ -39,12 +41,14 @@ except ImportError:
import urlparse import urlparse
cache_control_logger.setLevel(logging.ERROR)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PyPiRepository(Repository): class PyPiRepository(Repository):
CACHE_VERSION = parse_constraint("1.0.0b2") CACHE_VERSION = parse_constraint("1.0.0")
def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._url = url self._url = url
...@@ -63,9 +67,8 @@ class PyPiRepository(Repository): ...@@ -63,9 +67,8 @@ class PyPiRepository(Repository):
} }
) )
self._session = CacheControl( self._cache_control_cache = FileCache(str(release_cache_dir / "_http"))
session(), cache=FileCache(str(release_cache_dir / "_http")) self._session = CacheControl(session(), cache=self._cache_control_cache)
)
self._inspector = Inspector() self._inspector = Inspector()
super(PyPiRepository, self).__init__() super(PyPiRepository, self).__init__()
...@@ -357,7 +360,14 @@ class PyPiRepository(Repository): ...@@ -357,7 +360,14 @@ class PyPiRepository(Repository):
return data return data
def _get(self, endpoint): # type: (str) -> Union[dict, None] def _get(self, endpoint): # type: (str) -> Union[dict, None]
try:
json_response = self._session.get(self._url + endpoint) json_response = self._session.get(self._url + endpoint)
except TooManyRedirects:
# Cache control redirect loop.
# We try to remove the cache and try again
self._cache_control_cache.delete(self._url + endpoint)
json_response = self._session.get(self._url + endpoint)
if json_response.status_code == 404: if json_response.status_code == 404:
return None return None
......
...@@ -76,14 +76,12 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint ...@@ -76,14 +76,12 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
version = Version.parse(m.group(1)) version = Version.parse(m.group(1))
if precision == 2: if precision == 2:
low = version
high = version.stable.next_major high = version.stable.next_major
else: else:
low = Version(version.major, version.minor, version.patch)
high = version.stable.next_minor high = version.stable.next_minor
return VersionRange( return VersionRange(
low, high, include_min=True, always_include_max_prerelease=True version, high, include_min=True, always_include_max_prerelease=True
) )
# Caret range # Caret range
......
...@@ -12,6 +12,13 @@ except ImportError: ...@@ -12,6 +12,13 @@ except ImportError:
from glob import glob from glob import glob
try: try:
from importlib import metadata
import zipfile as zipp
except ImportError:
import importlib_metadata as metadata
import zipp
try:
import urllib.parse as urlparse import urllib.parse as urlparse
except ImportError: except ImportError:
import urlparse import urlparse
......
...@@ -54,7 +54,7 @@ class Exporter(object): ...@@ -54,7 +54,7 @@ class Exporter(object):
extras=None, extras=None,
with_credentials=False, with_credentials=False,
): # type: (Path, Union[IO, str], bool, bool, bool) -> None ): # type: (Path, Union[IO, str], bool, bool, bool) -> None
indexes = [] indexes = set()
content = "" content = ""
packages = self._poetry.locker.locked_repository(dev).packages packages = self._poetry.locker.locked_repository(dev).packages
...@@ -94,7 +94,7 @@ class Exporter(object): ...@@ -94,7 +94,7 @@ class Exporter(object):
dependency.marker = package.marker dependency.marker = package.marker
line = "{}".format(package.source_url) line = "{}".format(package.source_url)
if package.develop: if package.develop and package.source_type == "directory":
line = "-e " + line line = "-e " + line
else: else:
dependency = package.to_dependency() dependency = package.to_dependency()
...@@ -104,8 +104,11 @@ class Exporter(object): ...@@ -104,8 +104,11 @@ class Exporter(object):
if ";" in requirement: if ";" in requirement:
line += "; {}".format(requirement.split(";")[1].strip()) line += "; {}".format(requirement.split(";")[1].strip())
if package.source_type == "legacy" and package.source_url: if (
indexes.append(package.source_url) package.source_type not in {"git", "directory", "file", "url"}
and package.source_url
):
indexes.add(package.source_url)
if package.files and with_hashes: if package.files and with_hashes:
hashes = [] hashes = []
...@@ -131,10 +134,9 @@ class Exporter(object): ...@@ -131,10 +134,9 @@ class Exporter(object):
content += line content += line
if indexes: if indexes:
# If we have extra indexes, we add them to the begin # If we have extra indexes, we add them to the beginning of the output
# of the output
indexes_header = "" indexes_header = ""
for index in indexes: for index in sorted(indexes):
repository = [ repository = [
r r
for r in self._poetry.pool.repositories for r in self._poetry.pool.repositories
......
...@@ -8,11 +8,6 @@ from contextlib import contextmanager ...@@ -8,11 +8,6 @@ from contextlib import contextmanager
from typing import List from typing import List
from typing import Optional from typing import Optional
from keyring import delete_password
from keyring import get_password
from keyring import set_password
from keyring.errors import KeyringError
from poetry.config.config import Config from poetry.config.config import Config
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.version import Version from poetry.version import Version
...@@ -95,53 +90,6 @@ def parse_requires(requires): # type: (str) -> List[str] ...@@ -95,53 +90,6 @@ def parse_requires(requires): # type: (str) -> List[str]
return requires_dist return requires_dist
def keyring_service_name(repository_name): # type: (str) -> str
return "{}-{}".format("poetry-repository", repository_name)
def keyring_repository_password_get(
repository_name, username
): # type: (str, str) -> Optional[str]
try:
return get_password(keyring_service_name(repository_name), username)
except (RuntimeError, KeyringError):
return None
def keyring_repository_password_set(
repository_name, username, password
): # type: (str, str, str) -> None
try:
set_password(keyring_service_name(repository_name), username, password)
except (RuntimeError, KeyringError):
raise RuntimeError("Failed to store password in keyring")
def keyring_repository_password_del(
config, repository_name
): # type: (Config, str) -> None
try:
repo_auth = config.get("http-basic.{}".format(repository_name))
if repo_auth and "username" in repo_auth:
delete_password(
keyring_service_name(repository_name), repo_auth["username"]
)
except (RuntimeError, KeyringError):
pass
def get_http_basic_auth(
config, repository_name
): # type: (Config, str) -> Optional[tuple]
repo_auth = config.get("http-basic.{}".format(repository_name))
if repo_auth:
username, password = repo_auth["username"], repo_auth.get("password")
if password is None:
password = keyring_repository_password_get(repository_name, username)
return username, password
return None
def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path] def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
cert = config.get("certificates.{}.cert".format(repository_name)) cert = config.get("certificates.{}.cert".format(repository_name))
if cert: if cert:
...@@ -159,11 +107,17 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional ...@@ -159,11 +107,17 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional
def _on_rm_error(func, path, exc_info): def _on_rm_error(func, path, exc_info):
if not os.path.exists(path):
return
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
func(path) func(path)
def safe_rmtree(path): def safe_rmtree(path):
if Path(path).is_symlink():
return os.unlink(str(path))
shutil.rmtree(path, onerror=_on_rm_error) shutil.rmtree(path, onerror=_on_rm_error)
......
import logging
logger = logging.getLogger(__name__)
class PasswordManagerError(Exception):
pass
class KeyRingError(Exception):
pass
class KeyRing:
def __init__(self, namespace):
self._namespace = namespace
self._is_available = True
self._check()
def is_available(self):
return self._is_available
def get_password(self, name, username):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
return keyring.get_password(name, username)
except (RuntimeError, keyring.errors.KeyringError):
raise KeyRingError(
"Unable to retrieve the password for {} from the key ring".format(name)
)
def set_password(self, name, username, password):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
keyring.set_password(name, username, password)
except (RuntimeError, keyring.errors.KeyringError) as e:
raise KeyRingError(
"Unable to store the password for {} in the key ring: {}".format(
name, str(e)
)
)
def delete_password(self, name, username):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
keyring.delete_password(name, username)
except (RuntimeError, keyring.errors.KeyringError):
raise KeyRingError(
"Unable to delete the password for {} from the key ring".format(name)
)
def get_entry_name(self, name):
return "{}-{}".format(self._namespace, name)
def _check(self):
try:
import keyring
except Exception as e:
logger.debug("An error occurred while importing keyring: {}".format(str(e)))
self._is_available = False
return
backend = keyring.get_keyring()
name = backend.name.split(" ")[0]
if name == "fail":
logger.debug("No suitable keyring backend found")
self._is_available = False
elif "plaintext" in backend.name.lower():
logger.debug("Only a plaintext keyring backend is available. Not using it.")
self._is_available = False
elif name == "chainer":
try:
import keyring.backend
backends = keyring.backend.get_all_keyring()
self._is_available = any(
[
b.name.split(" ")[0] not in ["chainer", "fail"]
and "plaintext" not in b.name.lower()
for b in backends
]
)
except Exception:
self._is_available = False
if not self._is_available:
logger.warning("No suitable keyring backends were found")
class PasswordManager:
def __init__(self, config):
self._config = config
self._keyring = KeyRing("poetry-repository")
if not self._keyring.is_available():
logger.warning("Using a plaintext file to store and retrieve credentials")
@property
def keyring(self):
return self._keyring
def set_pypi_token(self, name, token):
if not self._keyring.is_available():
self._config.auth_config_source.add_property(
"pypi-token.{}".format(name), token
)
else:
self._keyring.set_password(name, "__token__", token)
def get_pypi_token(self, name):
if not self._keyring.is_available():
return self._config.get("pypi-token.{}".format(name))
return self._keyring.get_password(name, "__token__")
def delete_pypi_token(self, name):
if not self._keyring.is_available():
return self._config.auth_config_source.remove_property(
"pypi-token.{}".format(name)
)
self._keyring.delete_password(name, "__token__")
def get_http_auth(self, name):
auth = self._config.get("http-basic.{}".format(name))
if not auth:
return None
username, password = auth["username"], auth.get("password")
if password is None:
password = self._keyring.get_password(name, username)
return {
"username": username,
"password": password,
}
def set_http_password(self, name, username, password):
auth = {"username": username}
if not self._keyring.is_available():
auth["password"] = password
else:
self._keyring.set_password(name, username, password)
self._config.auth_config_source.add_property("http-basic.{}".format(name), auth)
def delete_http_password(self, name):
auth = self.get_http_auth(name)
if not auth or "username" not in auth:
return
try:
self._keyring.delete_password(name, auth["username"])
except KeyRingError:
pass
self._config.auth_config_source.remove_property("http-basic.{}".format(name))
...@@ -181,10 +181,11 @@ class SetupReader(object): ...@@ -181,10 +181,11 @@ class SetupReader(object):
continue continue
func = value.func func = value.func
if not isinstance(func, ast.Name): if not (isinstance(func, ast.Name) and func.id == "setup") and not (
continue isinstance(func, ast.Attribute)
and func.value.id == "setuptools"
if func.id != "setup": and func.attr == "setup"
):
continue continue
return value, elements return value, elements
......
...@@ -58,7 +58,9 @@ class Shell: ...@@ -58,7 +58,9 @@ class Shell:
self._path, ["-i"], dimensions=(terminal.height, terminal.width) self._path, ["-i"], dimensions=(terminal.height, terminal.width)
) )
if self._name == "zsh":
c.setecho(False) c.setecho(False)
activate_script = self._get_activate_script() activate_script = self._get_activate_script()
bin_dir = "Scripts" if WINDOWS else "bin" bin_dir = "Scripts" if WINDOWS else "bin"
activate_path = env.path / bin_dir / activate_script activate_path = env.path / bin_dir / activate_script
......
...@@ -2,9 +2,103 @@ ...@@ -2,9 +2,103 @@
import re import re
import subprocess import subprocess
from collections import namedtuple
from poetry.utils._compat import decode from poetry.utils._compat import decode
PATTERNS = [
re.compile(
r"(git\+)?"
r"((?P<protocol>\w+)://)"
r"((?P<user>\w+)@)?"
r"(?P<resource>[\w.\-]+)"
r"(:(?P<port>\d+))?"
r"(?P<pathname>(/(?P<owner>\w+)/)"
r"((?P<projects>([\w\-/]+)/)?(?P<name>[\w\-]+)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"^(git\+)?"
r"(?P<protocol>https?|git|ssh|rsync|file)://"
r"(?:(?P<user>.+)@)*"
r"(?P<resource>[a-z0-9_.-]*)"
r"(:?P<port>[\d]+)?"
r"(?P<pathname>[:/]((?P<owner>[\w\-]+)/(?P<projects>([\w\-/]+)/)?)?"
r"((?P<name>[\w\-.]+?)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"^(?:(?P<user>.+)@)*"
r"(?P<resource>[a-z0-9_.-]*)[:]*"
r"(?P<port>[\d]+)?"
r"(?P<pathname>/?(?P<owner>.+)/(?P<projects>([\w\-/]+)/)?(?P<name>.+).git)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"((?P<user>\w+)@)?"
r"(?P<resource>[\w.\-]+)"
r"[:/]{1,2}"
r"(?P<pathname>((?P<owner>\w+)/)?"
r"(?P<projects>([\w\-/]+)/)?"
r"((?P<name>[\w\-]+)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
]
class ParsedUrl:
def __init__(self, protocol, resource, pathname, user, port, name, rev):
self.protocol = protocol
self.resource = resource
self.pathname = pathname
self.user = user
self.port = port
self.name = name
self.rev = rev
@classmethod
def parse(cls, url): # type: () -> ParsedUrl
for pattern in PATTERNS:
m = pattern.match(url)
if m:
groups = m.groupdict()
return ParsedUrl(
groups.get("protocol"),
groups.get("resource"),
groups.get("pathname"),
groups.get("user"),
groups.get("port"),
groups.get("name"),
groups.get("rev"),
)
raise ValueError('Invalid git url "{}"'.format(url))
@property
def url(self): # type: () -> str
return "{}{}{}{}{}".format(
"{}://".format(self.protocol) if self.protocol else "",
"{}@".format(self.user) if self.user else "",
self.resource,
":{}".format(self.port) if self.port else "",
"/" + self.pathname.lstrip(":/"),
)
def format(self):
return "{}".format(self.url, "#{}".format(self.rev) if self.rev else "",)
def __str__(self): # type: () -> str
return self.format()
GitUrl = namedtuple("GitUrl", ["url", "revision"])
class GitConfig: class GitConfig:
def __init__(self, requires_git_presence=False): def __init__(self, requires_git_presence=False):
self._config = {} self._config = {}
...@@ -36,6 +130,30 @@ class Git: ...@@ -36,6 +130,30 @@ class Git:
self._config = GitConfig(requires_git_presence=True) self._config = GitConfig(requires_git_presence=True)
self._work_dir = work_dir self._work_dir = work_dir
@classmethod
def normalize_url(cls, url): # type: (str) -> GitUrl
parsed = ParsedUrl.parse(url)
formatted = re.sub(r"^git\+", "", url)
if parsed.rev:
formatted = re.sub(r"[#@]{}$".format(parsed.rev), "", formatted)
altered = parsed.format() != formatted
if altered:
if re.match(r"^git\+https?", url) and re.match(
r"^/?:[^0-9]", parsed.pathname
):
normalized = re.sub(r"git\+(.*:[^:]+):(.*)", "\\1/\\2", url)
elif re.match(r"^git\+file", url):
normalized = re.sub(r"git\+", "", url)
else:
normalized = re.sub(r"^(?:git\+)?ssh://", "", url)
else:
normalized = parsed.format()
return GitUrl(re.sub(r"#[^#]*$", "", normalized), parsed.rev)
@property @property
def config(self): # type: () -> GitConfig def config(self): # type: () -> GitConfig
return self._config return self._config
...@@ -93,9 +211,35 @@ class Git: ...@@ -93,9 +211,35 @@ class Git:
args += ["ls-files", "--others", "-i", "--exclude-standard"] args += ["ls-files", "--others", "-i", "--exclude-standard"]
output = self.run(*args) output = self.run(*args)
return output.split("\n") return output.strip().split("\n")
def remote_urls(self, folder=None): # type: (...) -> dict
output = self.run(
"config", "--get-regexp", r"remote\..*\.url", folder=folder
).strip()
urls = {}
for url in output.splitlines():
name, url = url.split(" ", 1)
urls[name.strip()] = url.strip()
return urls
def remote_url(self, folder=None): # type: (...) -> str
urls = self.remote_urls(folder=folder)
return urls.get("remote.origin.url", urls[list(urls.keys())[0]])
def run(self, *args, **kwargs): # type: (...) -> str
folder = kwargs.pop("folder", None)
if folder:
args = (
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
) + args
def run(self, *args): # type: (...) -> str
return decode( return decode(
subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT) subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT)
) ).strip()
...@@ -175,6 +175,12 @@ class BaseMarker(object): ...@@ -175,6 +175,12 @@ class BaseMarker(object):
def without_extras(self): # type: () -> BaseMarker def without_extras(self): # type: () -> BaseMarker
raise NotImplementedError() raise NotImplementedError()
def exclude(self, marker_name): # type: (str) -> BaseMarker
raise NotImplementedError()
def only(self, marker_name): # type: (str) -> BaseMarker
raise NotImplementedError()
def __repr__(self): def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self)) return "<{} {}>".format(self.__class__.__name__, str(self))
...@@ -198,12 +204,27 @@ class AnyMarker(BaseMarker): ...@@ -198,12 +204,27 @@ class AnyMarker(BaseMarker):
def without_extras(self): def without_extras(self):
return self return self
def exclude(self, marker_name): # type: (str) -> AnyMarker
return self
def only(self, marker_name): # type: (str) -> AnyMarker
return self
def __str__(self): def __str__(self):
return "" return ""
def __repr__(self): def __repr__(self):
return "<AnyMarker>" return "<AnyMarker>"
def __hash__(self):
return hash(("<any>", "<any>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, AnyMarker)
class EmptyMarker(BaseMarker): class EmptyMarker(BaseMarker):
def intersect(self, other): def intersect(self, other):
...@@ -224,12 +245,27 @@ class EmptyMarker(BaseMarker): ...@@ -224,12 +245,27 @@ class EmptyMarker(BaseMarker):
def without_extras(self): def without_extras(self):
return self return self
def exclude(self, marker_name): # type: (str) -> EmptyMarker
return self
def only(self, marker_name): # type: (str) -> EmptyMarker
return self
def __str__(self): def __str__(self):
return "<empty>" return "<empty>"
def __repr__(self): def __repr__(self):
return "<EmptyMarker>" return "<EmptyMarker>"
def __hash__(self):
return hash(("<empty>", "<empty>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, EmptyMarker)
class SingleMarker(BaseMarker): class SingleMarker(BaseMarker):
...@@ -329,7 +365,7 @@ class SingleMarker(BaseMarker): ...@@ -329,7 +365,7 @@ class SingleMarker(BaseMarker):
if self == other: if self == other:
return self return self
return MarkerUnion(self, other) return MarkerUnion.of(self, other)
return other.union(self) return other.union(self)
...@@ -343,7 +379,16 @@ class SingleMarker(BaseMarker): ...@@ -343,7 +379,16 @@ class SingleMarker(BaseMarker):
return self._constraint.allows(self._parser(environment[self._name])) return self._constraint.allows(self._parser(environment[self._name]))
def without_extras(self): def without_extras(self):
if self.name == "extra": return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
if self.name == marker_name:
return AnyMarker()
return self
def only(self, marker_name): # type: (str) -> BaseMarker
if self.name != marker_name:
return EmptyMarker() return EmptyMarker()
return self return self
...@@ -392,7 +437,7 @@ class MultiMarker(BaseMarker): ...@@ -392,7 +437,7 @@ class MultiMarker(BaseMarker):
markers = _flatten_markers(markers, MultiMarker) markers = _flatten_markers(markers, MultiMarker)
for marker in markers: for marker in markers:
if marker in new_markers or marker.is_empty(): if marker in new_markers:
continue continue
if isinstance(marker, SingleMarker): if isinstance(marker, SingleMarker):
...@@ -408,11 +453,9 @@ class MultiMarker(BaseMarker): ...@@ -408,11 +453,9 @@ class MultiMarker(BaseMarker):
intersection = mark.constraint.intersect(marker.constraint) intersection = mark.constraint.intersect(marker.constraint)
if intersection == mark.constraint: if intersection == mark.constraint:
intersected = True intersected = True
break
elif intersection == marker.constraint: elif intersection == marker.constraint:
new_markers[i] = marker new_markers[i] = marker
intersected = True intersected = True
break
elif intersection.is_empty(): elif intersection.is_empty():
return EmptyMarker() return EmptyMarker()
...@@ -421,9 +464,12 @@ class MultiMarker(BaseMarker): ...@@ -421,9 +464,12 @@ class MultiMarker(BaseMarker):
new_markers.append(marker) new_markers.append(marker)
if not new_markers: if any(m.is_empty() for m in new_markers) or not new_markers:
return EmptyMarker() return EmptyMarker()
if len(new_markers) == 1 and new_markers[0].is_any():
return AnyMarker()
return MultiMarker(*new_markers) return MultiMarker(*new_markers)
@property @property
...@@ -443,7 +489,7 @@ class MultiMarker(BaseMarker): ...@@ -443,7 +489,7 @@ class MultiMarker(BaseMarker):
def union(self, other): def union(self, other):
if isinstance(other, (SingleMarker, MultiMarker)): if isinstance(other, (SingleMarker, MultiMarker)):
return MarkerUnion(self, other) return MarkerUnion.of(self, other)
return other.union(self) return other.union(self)
...@@ -455,10 +501,32 @@ class MultiMarker(BaseMarker): ...@@ -455,10 +501,32 @@ class MultiMarker(BaseMarker):
return True return True
def without_extras(self): def without_extras(self):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = [] new_markers = []
for m in self._markers: for m in self._markers:
marker = m.without_extras() if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
if not marker.is_empty(): if not marker.is_empty():
new_markers.append(marker) new_markers.append(marker)
...@@ -493,17 +561,24 @@ class MultiMarker(BaseMarker): ...@@ -493,17 +561,24 @@ class MultiMarker(BaseMarker):
class MarkerUnion(BaseMarker): class MarkerUnion(BaseMarker):
def __init__(self, *markers): def __init__(self, *markers):
self._markers = [] self._markers = list(markers)
@property
def markers(self):
return self._markers
markers = _flatten_markers(markers, MarkerUnion) @classmethod
def of(cls, *markers): # type: (tuple) -> MarkerUnion
flattened_markers = _flatten_markers(markers, MarkerUnion)
for marker in markers: markers = []
if marker in self._markers: for marker in flattened_markers:
if marker in markers:
continue continue
if isinstance(marker, SingleMarker) and marker.name == "python_version": if isinstance(marker, SingleMarker) and marker.name == "python_version":
intersected = False intersected = False
for i, mark in enumerate(self._markers): for i, mark in enumerate(markers):
if ( if (
not isinstance(mark, SingleMarker) not isinstance(mark, SingleMarker)
or isinstance(mark, SingleMarker) or isinstance(mark, SingleMarker)
...@@ -516,18 +591,19 @@ class MarkerUnion(BaseMarker): ...@@ -516,18 +591,19 @@ class MarkerUnion(BaseMarker):
intersected = True intersected = True
break break
elif intersection == marker.constraint: elif intersection == marker.constraint:
self._markers[i] = marker markers[i] = marker
intersected = True intersected = True
break break
if intersected: if intersected:
continue continue
self._markers.append(marker) markers.append(marker)
@property if any(m.is_any() for m in markers):
def markers(self): return AnyMarker()
return self._markers
return MarkerUnion(*markers)
def append(self, marker): def append(self, marker):
if marker in self._markers: if marker in self._markers:
...@@ -557,7 +633,7 @@ class MarkerUnion(BaseMarker): ...@@ -557,7 +633,7 @@ class MarkerUnion(BaseMarker):
if not intersection.is_empty(): if not intersection.is_empty():
new_markers.append(intersection) new_markers.append(intersection)
return MarkerUnion(*new_markers) return MarkerUnion.of(*new_markers)
def union(self, other): def union(self, other):
if other.is_any(): if other.is_any():
...@@ -568,7 +644,7 @@ class MarkerUnion(BaseMarker): ...@@ -568,7 +644,7 @@ class MarkerUnion(BaseMarker):
new_markers = self._markers + [other] new_markers = self._markers + [other]
return MarkerUnion(*new_markers) return MarkerUnion.of(*new_markers)
def validate(self, environment): def validate(self, environment):
for m in self._markers: for m in self._markers:
...@@ -578,15 +654,37 @@ class MarkerUnion(BaseMarker): ...@@ -578,15 +654,37 @@ class MarkerUnion(BaseMarker):
return False return False
def without_extras(self): def without_extras(self):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = [] new_markers = []
for m in self._markers: for m in self._markers:
marker = m.without_extras() if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty(): if not marker.is_empty():
new_markers.append(marker) new_markers.append(marker)
return MarkerUnion(*new_markers) return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, MarkerUnion): if not isinstance(other, MarkerUnion):
...@@ -602,7 +700,15 @@ class MarkerUnion(BaseMarker): ...@@ -602,7 +700,15 @@ class MarkerUnion(BaseMarker):
return h return h
def __str__(self): def __str__(self):
return " or ".join(str(m) for m in self._markers) return " or ".join(
str(m) for m in self._markers if not m.is_any() and not m.is_empty()
)
def is_any(self):
return any(m.is_any() for m in self._markers)
def is_empty(self):
return all(m.is_empty() for m in self._markers)
def parse_marker(marker): def parse_marker(marker):
...@@ -654,4 +760,4 @@ def _compact_markers(markers): ...@@ -654,4 +760,4 @@ def _compact_markers(markers):
if len(groups) == 1: if len(groups) == 1:
return groups[0] return groups[0]
return MarkerUnion(*groups) return MarkerUnion.of(*groups)
...@@ -28,7 +28,7 @@ from .markers import parse_marker ...@@ -28,7 +28,7 @@ from .markers import parse_marker
try: try:
import urllib.parse as urlparse import urllib.parse as urlparse
except ImportError: except ImportError:
from urlparse import urlparse import urlparse
LEGACY_REGEX = r""" LEGACY_REGEX = r"""
......
...@@ -35,8 +35,7 @@ class VersionSelector(object): ...@@ -35,8 +35,7 @@ class VersionSelector(object):
dependency = Dependency(package_name, constraint) dependency = Dependency(package_name, constraint)
# Select highest version if we have many package = None
package = candidates[0]
for candidate in candidates: for candidate in candidates:
if ( if (
candidate.is_prerelease() candidate.is_prerelease()
...@@ -47,9 +46,11 @@ class VersionSelector(object): ...@@ -47,9 +46,11 @@ class VersionSelector(object):
continue continue
# Select highest version of the two # Select highest version of the two
if package.version < candidate.version: if package is None or package.version < candidate.version:
package = candidate package = candidate
if package is None:
return False
return package return package
def find_recommended_require_version(self, package): def find_recommended_require_version(self, package):
......
[tool.poetry] [tool.poetry]
name = "poetry" name = "poetry"
version = "1.0.0b3" version = "1.0.2"
description = "Python dependency management and packaging made easy." description = "Python dependency management and packaging made easy."
authors = [ authors = [
"Sébastien Eustace <sebastien@eustace.io>" "Sébastien Eustace <sebastien@eustace.io>"
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.md" readme = "README.md"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
...@@ -24,7 +24,7 @@ classifiers = [ ...@@ -24,7 +24,7 @@ classifiers = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7 || ^3.4"
cleo = "^0.7.6" cleo = "^0.7.6"
clikit = "^0.4.0" clikit = "^0.4.1"
requests = "^2.18" requests = "^2.18"
cachy = "^0.3.0" cachy = "^0.3.0"
requests-toolbelt = "^0.8.0" requests-toolbelt = "^0.8.0"
...@@ -46,15 +46,16 @@ pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } ...@@ -46,15 +46,16 @@ pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" }
# Use glob2 for Python 2.7 and 3.4 # Use glob2 for Python 2.7 and 3.4
glob2 = { version = "^0.6", python = "~2.7 || ~3.4" } glob2 = { version = "^0.6", python = "~2.7 || ~3.4" }
# Use virtualenv for Python 2.7 since venv does not exist # Use virtualenv for Python 2.7 since venv does not exist
virtualenv = { version = "^16.0", python = "~2.7" } virtualenv = { version = "^16.7.9", python = "~2.7" }
# functools32 is needed for Python 2.7 # functools32 is needed for Python 2.7
functools32 = { version = "^3.2.3", python = "~2.7" } functools32 = { version = "^3.2.3", python = "~2.7" }
keyring = [ keyring = [
{ version = "^18.0", python = "~2.7 || ~3.4" }, { version = "^18.0.1", python = "~2.7 || ~3.4" },
{ version = "^19.0", python = "^3.5" } { version = "^20.0.1", python = "^3.5" }
] ]
# Use subprocess32 for Python 2.7 and 3.4 # Use subprocess32 for Python 2.7 and 3.4
subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" } subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" }
importlib-metadata = {version = "~1.1.3", python = "<3.8"}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^4.1" pytest = "^4.1"
...@@ -69,11 +70,20 @@ pre-commit = "^1.10" ...@@ -69,11 +70,20 @@ pre-commit = "^1.10"
tox = "^3.0" tox = "^3.0"
pytest-sugar = "^0.9.2" pytest-sugar = "^0.9.2"
httpretty = "^0.9.6" httpretty = "^0.9.6"
markdown-include = "^0.5.1"
[tool.poetry.scripts] [tool.poetry.scripts]
poetry = "poetry.console:main" poetry = "poetry.console:main"
[build-system]
requires = ["intreehooks"]
build-backend = "intreehooks:loader"
[tool.intreehooks]
build-backend = "poetry.masonry.api"
[tool.isort] [tool.isort]
line_length = 88 line_length = 88
force_single_line = true force_single_line = true
......
...@@ -219,6 +219,9 @@ class MakeReleaseCommand(Command): ...@@ -219,6 +219,9 @@ class MakeReleaseCommand(Command):
subprocess.check_output( subprocess.check_output(
[python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS
) )
if version == "3.4" and WINDOWS:
continue
subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"]) subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"])
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise RuntimeError("Python {} is not available".format(version)) raise RuntimeError("Python {} is not available".format(version))
......
...@@ -10,15 +10,9 @@ import pytest ...@@ -10,15 +10,9 @@ import pytest
from poetry.config.config import Config as BaseConfig from poetry.config.config import Config as BaseConfig
from poetry.config.dict_config_source import DictConfigSource from poetry.config.dict_config_source import DictConfigSource
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from tests.helpers import mock_clone
from tests.helpers import mock_download
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
class Config(BaseConfig): class Config(BaseConfig):
...@@ -58,6 +52,11 @@ def auth_config_source(): ...@@ -58,6 +52,11 @@ def auth_config_source():
@pytest.fixture @pytest.fixture
def config(config_source, auth_config_source, mocker): def config(config_source, auth_config_source, mocker):
import keyring
from keyring.backends.fail import Keyring
keyring.set_keyring(Keyring())
c = Config() c = Config()
c.merge(config_source.config) c.merge(config_source.config)
c.set_config_source(config_source) c.set_config_source(config_source)
...@@ -69,47 +68,6 @@ def config(config_source, auth_config_source, mocker): ...@@ -69,47 +68,6 @@ def config(config_source, auth_config_source, mocker):
return c return c
def mock_clone(_, source, dest):
# Checking source to determine which folder we need to copy
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent
/ "fixtures"
/ "git"
/ parts.netloc
/ parts.path.lstrip("/").rstrip(".git")
)
if dest.exists():
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
if dest.exists():
os.unlink(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does. os.symlink requires either administrative
# privileges or developer mode on Win10, throwing an OSError is neither is active.
if WINDOWS:
if PY2:
shutil.copyfile(str(fixture), str(dest))
else:
try:
os.symlink(str(fixture), str(dest))
except OSError:
shutil.copyfile(str(fixture), str(dest))
else:
os.symlink(str(fixture), str(dest))
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def download_mock(mocker): def download_mock(mocker):
# Patch download to not download anything but to just copy from fixtures # Patch download to not download anything but to just copy from fixtures
......
...@@ -11,7 +11,7 @@ def test_about(app): ...@@ -11,7 +11,7 @@ def test_about(app):
Poetry - Package Management for Python Poetry - Package Management for Python
Poetry is a dependency manager tracking local dependencies of your projects and libraries. Poetry is a dependency manager tracking local dependencies of your projects and libraries.
See https://github.com/sdispater/poetry for more information. See https://github.com/python-poetry/poetry for more information.
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -109,7 +109,7 @@ def test_add_constraint_with_extras(app, repo, installer): ...@@ -109,7 +109,7 @@ def test_add_constraint_with_extras(app, repo, installer):
repo.add_package(cachy1) repo.add_package(cachy1)
repo.add_package(get_package("msgpack-python", "0.5.3")) repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy[msgpack]^0.1.0") tester.execute("cachy[msgpack]>=0.1.0,<0.2.0")
expected = """\ expected = """\
...@@ -264,6 +264,42 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -264,6 +264,42 @@ Package operations: 4 installs, 0 updates, 0 removals
} }
def test_add_git_ssh_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+ssh://git@github.com/demo/demo.git@develop")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 2
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "ssh://git@github.com/demo/demo.git",
"rev": "develop",
}
def test_add_directory_constraint(app, repo, installer, mocker): def test_add_directory_constraint(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd") p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".." p.return_value = Path(__file__) / ".."
...@@ -456,7 +492,7 @@ def test_add_url_constraint_wheel(app, repo, installer, mocker): ...@@ -456,7 +492,7 @@ def test_add_url_constraint_wheel(app, repo, installer, mocker):
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute( tester.execute(
"https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
) )
expected = """\ expected = """\
...@@ -470,7 +506,7 @@ Writing lock file ...@@ -470,7 +506,7 @@ Writing lock file
Package operations: 2 installs, 0 updates, 0 removals Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4) - Installing pendulum (1.4.4)
- Installing demo (0.1.0 https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl) - Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -481,7 +517,7 @@ Package operations: 2 installs, 0 updates, 0 removals ...@@ -481,7 +517,7 @@ Package operations: 2 installs, 0 updates, 0 removals
assert "demo" in content["dependencies"] assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == { assert content["dependencies"]["demo"] == {
"url": "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" "url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
} }
...@@ -494,7 +530,7 @@ def test_add_url_constraint_wheel_with_extras(app, repo, installer, mocker): ...@@ -494,7 +530,7 @@ def test_add_url_constraint_wheel_with_extras(app, repo, installer, mocker):
repo.add_package(get_package("tomlkit", "0.5.5")) repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute( tester.execute(
"https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]" "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]"
) )
expected = """\ expected = """\
...@@ -510,7 +546,7 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -510,7 +546,7 @@ Package operations: 4 installs, 0 updates, 0 removals
- Installing cleo (0.6.5) - Installing cleo (0.6.5)
- Installing pendulum (1.4.4) - Installing pendulum (1.4.4)
- Installing tomlkit (0.5.5) - Installing tomlkit (0.5.5)
- Installing demo (0.1.0 https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl) - Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -521,7 +557,7 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -521,7 +557,7 @@ Package operations: 4 installs, 0 updates, 0 removals
assert "demo" in content["dependencies"] assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == { assert content["dependencies"]["demo"] == {
"url": "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl", "url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
"extras": ["foo", "bar"], "extras": ["foo", "bar"],
} }
......
...@@ -36,14 +36,14 @@ def test_check_invalid(app, mocker): ...@@ -36,14 +36,14 @@ def test_check_invalid(app, mocker):
Error: u'description' is a required property Error: u'description' is a required property
Error: INVALID is not a valid license Error: INVALID is not a valid license
Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-preleases" instead. Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead.
""" """
else: else:
expected = """\ expected = """\
Error: 'description' is a required property Error: 'description' is a required property
Error: INVALID is not a valid license Error: INVALID is not a valid license
Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-preleases" instead. Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead.
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -26,9 +26,9 @@ license = "MIT" ...@@ -26,9 +26,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -379,6 +379,178 @@ cachy 0.1.0 0.2.0 Cachy package ...@@ -379,6 +379,178 @@ cachy 0.1.0 0.2.0 Cachy package
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
def test_show_outdated_with_only_up_to_date_packages(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
installed.add_package(cachy_020)
repo.add_package(cachy_020)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.2.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": []},
},
}
)
tester.execute("--outdated")
expected = ""
assert expected == tester.io.fetch_output()
def test_show_outdated_has_prerelease_but_not_allowed(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_010 = get_package("cachy", "0.1.0")
cachy_010.description = "Cachy package"
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
cachy_030dev = get_package("cachy", "0.3.0.dev123")
cachy_030dev.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
installed.add_package(cachy_010)
installed.add_package(pendulum_200)
# sorting isn't used, so this has to be the first element to
# replicate the issue in PR #1548
repo.add_package(cachy_030dev)
repo.add_package(cachy_010)
repo.add_package(cachy_020)
repo.add_package(pendulum_200)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": []},
},
}
)
tester.execute("--outdated")
expected = """\
cachy 0.1.0 0.2.0 Cachy package
"""
assert expected == tester.io.fetch_output()
def test_show_outdated_has_prerelease_and_allowed(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_010dev = get_package("cachy", "0.1.0.dev1")
cachy_010dev.description = "Cachy package"
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
cachy_030dev = get_package("cachy", "0.3.0.dev123")
cachy_030dev.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
installed.add_package(cachy_010dev)
installed.add_package(pendulum_200)
# sorting isn't used, so this has to be the first element to
# replicate the issue in PR #1548
repo.add_package(cachy_030dev)
repo.add_package(cachy_010dev)
repo.add_package(cachy_020)
repo.add_package(pendulum_200)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0.dev1",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": []},
},
}
)
tester.execute("--outdated")
expected = """\
cachy 0.1.0.dev1 0.3.0.dev123 Cachy package
"""
assert expected == tester.io.fetch_output()
def test_show_outdated_formatting(app, poetry, installed, repo): def test_show_outdated_formatting(app, poetry, installed, repo):
command = app.find("show") command = app.find("show")
tester = CommandTester(command) tester = CommandTester(command)
......
...@@ -44,4 +44,4 @@ def test_version_show(app): ...@@ -44,4 +44,4 @@ def test_version_show(app):
command = app.find("version") command = app.find("version")
tester = CommandTester(command) tester = CommandTester(command)
tester.execute() tester.execute()
assert "Project (simple-project) version is 1.2.3\n" == tester.io.fetch_output() assert "simple-project 1.2.3\n" == tester.io.fetch_output()
import os import os
import shutil
import pytest import pytest
...@@ -13,16 +12,10 @@ from poetry.poetry import Poetry as BasePoetry ...@@ -13,16 +12,10 @@ from poetry.poetry import Poetry as BasePoetry
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository as BaseRepository from poetry.repositories import Repository as BaseRepository
from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.exceptions import PackageNotFound
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from tests.helpers import mock_clone
from tests.helpers import mock_download
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
@pytest.fixture() @pytest.fixture()
...@@ -30,45 +23,6 @@ def installer(): ...@@ -30,45 +23,6 @@ def installer():
return NoopInstaller() return NoopInstaller()
def mock_clone(self, source, dest):
# Checking source to determine which folder we need to copy
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ parts.netloc
/ parts.path.lstrip("/").rstrip(".git")
)
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent.parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
if dest.exists():
shutil.rmtree(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does. os.symlink requires either administrative
# privileges or developer mode on Win10, throwing an OSError is neither is active.
if WINDOWS:
if PY2:
shutil.copyfile(str(fixture), str(dest))
else:
try:
os.symlink(str(fixture), str(dest))
except OSError:
shutil.copyfile(str(fixture), str(dest))
else:
os.symlink(str(fixture), str(dest))
@pytest.fixture @pytest.fixture
def installed(): def installed():
return BaseRepository() return BaseRepository()
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -7,9 +7,9 @@ authors = [ ...@@ -7,9 +7,9 @@ authors = [
] ]
license = "MIT" license = "MIT"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
[virtualenvs] [virtualenvs]
in-project = false
create = false create = false
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
import os
import shutil
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import Package from poetry.packages import Package
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import urlparse
from poetry.vcs.git import ParsedUrl
FIXTURE_PATH = Path(__file__).parent / "fixtures" FIXTURE_PATH = Path(__file__).parent / "fixtures"
...@@ -27,3 +34,57 @@ def fixture(path=None): ...@@ -27,3 +34,57 @@ def fixture(path=None):
return FIXTURE_PATH / path return FIXTURE_PATH / path
else: else:
return FIXTURE_PATH return FIXTURE_PATH
def copy_or_symlink(source, dest):
if dest.exists():
if dest.is_symlink():
os.unlink(str(dest))
elif dest.is_dir():
shutil.rmtree(str(dest))
else:
os.unlink(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does.
# os.symlink requires either administrative privileges or developer mode on Win10,
# throwing an OSError if neither is active.
if WINDOWS:
if PY2:
if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))
else:
try:
os.symlink(str(source), str(dest), target_is_directory=source.is_dir())
except OSError:
if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))
else:
os.symlink(str(source), str(dest))
def mock_clone(_, source, dest):
# Checking source to determine which folder we need to copy
parsed = ParsedUrl.parse(source)
folder = (
Path(__file__).parent
/ "fixtures"
/ "git"
/ parsed.resource
/ parsed.pathname.lstrip("/").rstrip(".git")
)
copy_or_symlink(folder, dest)
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
copy_or_symlink(fixture, dest)
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-extras" name = "project-with-extras"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -18,6 +19,7 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies/. ...@@ -18,6 +19,7 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies/.
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-transitive-directory-dependencies" name = "project-with-transitive-directory-dependencies"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,6 +9,7 @@ version = "1.4.4" ...@@ -9,6 +9,7 @@ version = "1.4.4"
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-extras" name = "project-with-extras"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,6 +9,7 @@ python-versions = "*" ...@@ -9,6 +9,7 @@ python-versions = "*"
[[package]] [[package]]
name = "my-package" name = "my-package"
version = "0.1.2" version = "0.1.2"
develop = true
description = "Demo project." description = "Demo project."
category = "main" category = "main"
optional = false optional = false
......
...@@ -23,6 +23,7 @@ C = "1.5" ...@@ -23,6 +23,7 @@ C = "1.5"
[[package]] [[package]]
name = "C" name = "C"
version = "1.5" version = "1.5"
marker = "python_version >= \"2.7\""
description = "" description = ""
category = "main" category = "main"
optional = false optional = false
......
...@@ -29,6 +29,7 @@ version = "1.4.4" ...@@ -29,6 +29,7 @@ version = "1.4.4"
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-transitive-file-dependencies" name = "project-with-transitive-file-dependencies"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,7 +9,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" ...@@ -9,7 +9,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.source] [package.source]
type = "url" type = "url"
reference = "" reference = ""
url = "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
[package.dependencies] [package.dependencies]
pendulum = ">=1.4.4" pendulum = ">=1.4.4"
......
...@@ -25,6 +25,9 @@ from tests.repositories.test_legacy_repository import ( ...@@ -25,6 +25,9 @@ from tests.repositories.test_legacy_repository import (
from tests.repositories.test_pypi_repository import MockRepository from tests.repositories.test_pypi_repository import MockRepository
fixtures_dir = Path("tests/fixtures")
class Installer(BaseInstaller): class Installer(BaseInstaller):
def _get_installer(self): def _get_installer(self):
return NoopInstaller() return NoopInstaller()
...@@ -662,7 +665,7 @@ def test_installer_with_pypi_repository(package, locker, installed): ...@@ -662,7 +665,7 @@ def test_installer_with_pypi_repository(package, locker, installed):
def test_run_installs_with_local_file(installer, locker, repo, package): def test_run_installs_with_local_file(installer, locker, repo, package):
file_path = Path("tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl") file_path = fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"file": str(file_path)}) package.add_dependency("demo", {"file": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
...@@ -677,8 +680,8 @@ def test_run_installs_with_local_file(installer, locker, repo, package): ...@@ -677,8 +680,8 @@ def test_run_installs_with_local_file(installer, locker, repo, package):
def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package): def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package):
file_path = Path( file_path = (
"tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" fixtures_dir / "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl"
) )
package.add_dependency("demo", {"file": str(file_path)}) package.add_dependency("demo", {"file": str(file_path)})
...@@ -694,7 +697,7 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa ...@@ -694,7 +697,7 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa
def test_run_installs_with_local_poetry_directory_and_extras( def test_run_installs_with_local_poetry_directory_and_extras(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_extras") file_path = fixtures_dir / "project_with_extras"
package.add_dependency( package.add_dependency(
"project-with-extras", {"path": str(file_path), "extras": ["extras_a"]} "project-with-extras", {"path": str(file_path), "extras": ["extras_a"]}
) )
...@@ -713,8 +716,8 @@ def test_run_installs_with_local_poetry_directory_and_extras( ...@@ -713,8 +716,8 @@ def test_run_installs_with_local_poetry_directory_and_extras(
def test_run_installs_with_local_poetry_directory_transitive( def test_run_installs_with_local_poetry_directory_transitive(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path( file_path = (
"tests/fixtures/directory/project_with_transitive_directory_dependencies/" fixtures_dir / "directory/project_with_transitive_directory_dependencies/"
) )
package.add_dependency( package.add_dependency(
"project-with-transitive-directory-dependencies", {"path": str(file_path)} "project-with-transitive-directory-dependencies", {"path": str(file_path)}
...@@ -735,9 +738,7 @@ def test_run_installs_with_local_poetry_directory_transitive( ...@@ -735,9 +738,7 @@ def test_run_installs_with_local_poetry_directory_transitive(
def test_run_installs_with_local_poetry_file_transitive( def test_run_installs_with_local_poetry_file_transitive(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path( file_path = fixtures_dir / "directory/project_with_transitive_file_dependencies/"
"tests/fixtures/directory/project_with_transitive_file_dependencies/"
)
package.add_dependency( package.add_dependency(
"project-with-transitive-file-dependencies", {"path": str(file_path)} "project-with-transitive-file-dependencies", {"path": str(file_path)}
) )
...@@ -757,7 +758,7 @@ def test_run_installs_with_local_poetry_file_transitive( ...@@ -757,7 +758,7 @@ def test_run_installs_with_local_poetry_file_transitive(
def test_run_installs_with_local_setuptools_directory( def test_run_installs_with_local_setuptools_directory(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_setup/") file_path = fixtures_dir / "project_with_setup/"
package.add_dependency("my-package", {"path": str(file_path)}) package.add_dependency("my-package", {"path": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
...@@ -1182,8 +1183,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1182,8 +1183,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"checksum": [], "checksum": [],
"dependencies": { "dependencies": {
"B": [ "B": [
{"version": "^1.0", "python": "<4.0"}, {"version": "^1.0", "python": "<2.7"},
{"version": "^2.0", "python": ">=4.0"}, {"version": "^2.0", "python": ">=2.7"},
] ]
}, },
}, },
...@@ -1196,7 +1197,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1196,7 +1197,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"python-versions": "*", "python-versions": "*",
"checksum": [], "checksum": [],
"dependencies": {"C": "1.2"}, "dependencies": {"C": "1.2"},
"requirements": {"python": "<4.0"}, "requirements": {"python": "<2.7"},
}, },
{ {
"name": "B", "name": "B",
...@@ -1207,7 +1208,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1207,7 +1208,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"python-versions": "*", "python-versions": "*",
"checksum": [], "checksum": [],
"dependencies": {"C": "1.5"}, "dependencies": {"C": "1.5"},
"requirements": {"python": ">=4.0"}, "requirements": {"python": ">=2.7"},
}, },
{ {
"name": "C", "name": "C",
...@@ -1516,7 +1517,7 @@ def test_installer_can_install_dependencies_from_forced_source( ...@@ -1516,7 +1517,7 @@ def test_installer_can_install_dependencies_from_forced_source(
def test_run_installs_with_url_file(installer, locker, repo, package): def test_run_installs_with_url_file(installer, locker, repo, package):
url = "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"url": url}) package.add_dependency("demo", {"url": url})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment