Repository: python-poetry/poetry

Issue:
Home directory based virtualenvs path (~/directory/path) creates a ~ directory in the project root
Hi! 
First of all, thanks a lot for a great tool, I can't say enough how I like it!
Now, to the ticket

- [x] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate.

- **OS version and name**: Arch Linux
- **Poetry version**: `0.12.16`
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: https://gist.github.com/valignatev/c3776ec5677644e6a68534d77eb20c2b

## Issue
When configuring `virtualenvs.path` in the `~/.config/pypoetry/config.toml` with the tilde to specify (for example, `~/.virtualenvs`, Poetry creates a `~/.virtualenws` directories in the project root instead. More specifically, considering this project structure:
```
.
├── proj
│   └── __init__.py
├── poetry.lock
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_proj.py
```
When we do `poetry install` in the project root, it becomes this:
```
.
├── ~
│   └── .virtualenvs
├── proj
│   └── __init__.py
├── poetry.lock
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_proj.py
```

The contents of a `config.toml`:

```toml
[settings]
virtualenvs.path = "~/.virtualenvs"

[repositories]
```

Absolute path in the `virtualenvs.path` works, of course.

I've found out why this happens. Here: https://github.com/sdispater/poetry/blob/master/poetry/utils/env.py#L202

Without the `expanduser` call, `~` will be treated like a regular directory name. 

This should be pretty easy to fix, so I can submit a PR. I'm not sure though if adding a test would be easy as well, since I'm not very familliar with Poetry test suit yet :)

Let me know if I should do it. Or maybe for some reason you don't want to support `~/` paths :)


Discussion:
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.

I can fix merge conflicts again if you're still interested in getting it fixed
Similar issue happens for

```
[tool.poetry.dependencies]
my-package = {path = "~/my/path", develop = true}
```


Resolving patch:
diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py
--- a/src/poetry/config/config.py
+++ b/src/poetry/config/config.py
@@ -206,6 +206,13 @@ def _get_environment_repositories() -> dict[str, dict[str, str]]:
     def repository_cache_directory(self) -> Path:
         return Path(self.get("cache-dir")) / "cache" / "repositories"
 
+    @property
+    def virtualenvs_path(self) -> Path:
+        path = self.get("virtualenvs.path")
+        if path is None:
+            path = Path(self.get("cache-dir")) / "virtualenvs"
+        return Path(path).expanduser()
+
     def get(self, setting_name: str, default: Any = None) -> Any:
         """
         Retrieve a setting value.
diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py
--- a/src/poetry/utils/env.py
+++ b/src/poetry/utils/env.py
@@ -552,12 +552,7 @@ def _detect_active_python(self, io: IO) -> str | None:
         return executable
 
     def activate(self, python: str, io: IO) -> Env:
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
-
+        venv_path = self._poetry.config.virtualenvs_path
         cwd = self._poetry.file.parent
 
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
@@ -645,12 +640,7 @@ def activate(self, python: str, io: IO) -> Env:
         return self.get(reload=True)
 
     def deactivate(self, io: IO) -> None:
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
-
+        venv_path = self._poetry.config.virtualenvs_path
         name = self._poetry.package.name
         name = self.generate_env_name(name, str(self._poetry.file.parent))
 
@@ -671,11 +661,7 @@ def get(self, reload: bool = False) -> Env:
 
         python_minor = ".".join([str(v) for v in sys.version_info[:2]])
 
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
+        venv_path = self._poetry.config.virtualenvs_path
 
         cwd = self._poetry.file.parent
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
@@ -712,11 +698,7 @@ def get(self, reload: bool = False) -> Env:
             if not create_venv:
                 return self.get_system_env()
 
-            venv_path = self._poetry.config.get("virtualenvs.path")
-            if venv_path is None:
-                venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-            else:
-                venv_path = Path(venv_path)
+            venv_path = self._poetry.config.virtualenvs_path
 
             name = f"{base_env_name}-py{python_minor.strip()}"
 
@@ -741,13 +723,7 @@ def list(self, name: str | None = None) -> list[VirtualEnv]:
             name = self._poetry.package.name
 
         venv_name = self.generate_env_name(name, str(self._poetry.file.parent))
-
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
-
+        venv_path = self._poetry.config.virtualenvs_path
         env_list = [
             VirtualEnv(Path(p)) for p in sorted(venv_path.glob(f"{venv_name}-py*"))
         ]
@@ -762,11 +738,7 @@ def list(self, name: str | None = None) -> list[VirtualEnv]:
         return env_list
 
     def remove(self, python: str) -> Env:
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
+        venv_path = self._poetry.config.virtualenvs_path
 
         cwd = self._poetry.file.parent
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
@@ -875,7 +847,6 @@ def create_venv(
 
         create_venv = self._poetry.config.get("virtualenvs.create")
         root_venv = self._poetry.config.get("virtualenvs.in-project")
-        venv_path = self._poetry.config.get("virtualenvs.path")
         prefer_active_python = self._poetry.config.get(
             "virtualenvs.prefer-active-python"
         )
@@ -884,13 +855,7 @@ def create_venv(
         if not executable and prefer_active_python:
             executable = self._detect_active_python(io)
 
-        if root_venv:
-            venv_path = cwd / ".venv"
-        elif venv_path is None:
-            venv_path = self._poetry.config.get("cache-dir") / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
-
+        venv_path = cwd / ".venv" if root_venv else self._poetry.config.virtualenvs_path
         if not name:
             name = self._poetry.package.name
         assert name is not None


Test patch:
diff --git a/tests/config/test_config.py b/tests/config/test_config.py
--- a/tests/config/test_config.py
+++ b/tests/config/test_config.py
@@ -3,6 +3,7 @@
 import os
 import re
 
+from pathlib import Path
 from typing import TYPE_CHECKING
 
 import pytest
@@ -17,7 +18,6 @@
 if TYPE_CHECKING:
     from collections.abc import Callable
     from collections.abc import Iterator
-    from pathlib import Path
 
 
 def get_options_based_on_normalizer(normalizer: Callable) -> str:
@@ -66,3 +66,14 @@ def test_config_get_from_environment_variable(
 ):
     os.environ[env_var] = env_value
     assert config.get(name) is value
+
+
+@pytest.mark.parametrize(
+    ("path_config", "expected"),
+    [("~/.venvs", Path.home() / ".venvs"), ("venv", Path("venv"))],
+)
+def test_config_expands_tilde_for_virtualenvs_path(
+    config: Config, path_config: str, expected: Path
+):
+    config.merge({"virtualenvs": {"path": path_config}})
+    assert config.virtualenvs_path == expected